diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 90f898fc362..905280e3eab 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest container: - image: arduino/arduino-cli:builder-0.1 + image: arduino/arduino-cli:builder-1 volumes: # cache go dependencies across pipeline's steps - $PWD/go:/go diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c96773b179b..cac5a20e6a5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest container: - image: arduino/arduino-cli:builder-0.1 + image: arduino/arduino-cli:builder-1 volumes: # cache go dependencies across pipeline's steps - $PWD/go:/go diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 00000000000..119a95e8703 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,107 @@ +name: stale-bot + +on: + schedule: + # run every day at midnight + - cron: '0 0 * * *' + issue_comment: + types: ['created'] + +jobs: + stale-bot: + runs-on: ubuntu-latest + steps: + - name: Mark stale + if: github.event_name == 'schedule' + uses: actions/github-script@0.2.0 + with: + github-token: ${{github.token}} + script: | + // Get a list of all open issues labeled `waiting for feedback` + const opts = github.issues.listForRepo.endpoint.merge({ + ...context.repo, + state: 'open', + labels: ['waiting for feedback'], + }); + const issues = await github.paginate(opts); + + // Set this value to whatever makes sense for the repo. + let elapsedDays = 15 + + let elapsed = elapsedDays * 24 * 60 * 60 * 1000; + let now = new Date().getTime(); + for (const issue of issues) { + // If an issue was active in the past 15 days, leave it alone. + if (now - new Date(issue.updated_at).getTime() < elapsed) { + continue; + } + + // If we're here, we've been waiting for feedback for more than + // 15 days, mark as stale. + github.issues.addLabels({ + ...context.repo, + issue_number: issue.number, + labels: ['stale'] + }); + } + + - name: Mark active + if: github.event_name == 'issue_comment' + uses: actions/github-script@0.2.0 + with: + github-token: ${{github.token}} + script: | + // Every time a comment is added to an issue, close it if it contains + // the `stale` label. + + // Load issue's labels. + const opts = github.issues.listLabelsOnIssue.endpoint.merge({ + ...context.repo, + issue_number: context.issue.number + }); + const labels = await github.paginate(opts); + + // Search for `stale`. + for (const label of labels) { + if (label.name === 'stale') { + await github.issues.removeLabel({ + ...context.repo, + issue_number: context.issue.number, + name: 'stale' + }) + return; + } + } + + - name: Close stale + if: github.event_name == 'schedule' + uses: actions/github-script@0.2.0 + with: + github-token: ${{github.token}} + script: | + // Load all the `stale` issues + const opts = github.issues.listForRepo.endpoint.merge({ + ...context.repo, + state: 'open', + labels: ['stale'], + }); + const issues = await github.paginate(opts); + + // Set this value to whatever makes sense for the repo. + let elapsedDays = 30; + + let elapsed = elapsedDays * 24 * 60 * 60 * 1000; + let now = new Date().getTime(); + for (const issue of issues) { + // If an issue was stale for less than elapsed time, leave it alone. + if (now - new Date(issue.updated_at).getTime() < elapsed) { + continue; + } + + // Close the stale issue. + await github.issues.update({ + ...context.repo, + issue_number: issue.number, + state: 'closed' + }); + } diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 166cb2ac226..ce75a07f1af 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,13 +1,17 @@ name: test -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: jobs: test-matrix: strategy: matrix: - operating-system: [ubuntu-18.04, windows-2019] + operating-system: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.operating-system }} @@ -21,22 +25,30 @@ jobs: - name: Install Go uses: actions/setup-go@v1 with: - go-version: '1.12.x' + go-version: '1.13' - name: Install Go deps + # Since 10/23/2019 pwsh is the default shell + # on Windows, but pwsh fails to install protoc-gen-go so + # we force bash as default shell for all OSes in this task run: | go get github.com/golangci/govet go get golang.org/x/lint/golint go get github.com/golang/protobuf/protoc-gen-go + shell: bash - name: Install Taskfile uses: Arduino/actions/setup-taskfile@master + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Check the code is good run: task check - name: Install protoc compiler - uses: Arduino/actions/setup-protoc@master + uses: arduino/setup-protoc@v1.1.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Check protocol buffers compile correctly run: task protoc @@ -48,13 +60,14 @@ jobs: run: task test-unit - name: Run unit tests on the legacy package - if: matrix.operating-system != 'windows-2019' + # Run legacy tests on one platform only + if: matrix.operating-system == 'ubuntu-latest' run: task test-legacy - name: Install Python uses: actions/setup-python@v1 with: - python-version: '3.x' + python-version: '3.7' architecture: 'x64' - name: Run integration tests @@ -68,7 +81,7 @@ jobs: # Codecov whitelists GitHub, lifting the need # for a token. if: > - matrix.operating-system != 'windows-2019' && + matrix.operating-system == 'ubuntu-latest' && github.event_name == 'push' uses: codecov/codecov-action@v1.0.2 with: @@ -82,7 +95,7 @@ jobs: # Codecov whitelists GitHub, lifting the need # for a token. if: > - matrix.operating-system != 'windows-2019' && + matrix.operating-system == 'ubuntu-latest' && github.event_name == 'push' uses: codecov/codecov-action@v1.0.2 with: @@ -96,7 +109,7 @@ jobs: # Codecov whitelists GitHub, lifting the need # for a token. if: > - matrix.operating-system != 'windows-2019' && + matrix.operating-system == 'ubuntu-latest' && github.event_name == 'push' uses: codecov/codecov-action@v1.0.2 with: diff --git a/.gitignore b/.gitignore index a7ce19bd44a..935d14fe465 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ __pycache__ venv .pytest_cache /dist +/.pytest-tmp-dir # gRPC client example folder /client_example/client_example diff --git a/.goreleaser.yml b/.goreleaser.yml index 38e663941ee..1cff66d505a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -11,10 +11,10 @@ release: changelog: filters: exclude: - - '^\[skip changelog\].*' - - '^\[changelog skip\].*' - - '^\[skip ci\].*' - - '^\[ci skip\].*' + # [skip changelog], [skip-changelog], [changelog skip], [changelog-skip] + - '^(?i)\[(skip|changelog)[ ,-](skip|changelog)\].*' + # [skip ci], [skip-ci], [ci skip], [ci-skip] + - '^(?i)\[(skip|ci)[ ,-](skip|ci)\].*' # We have multiple builds in order to fine tune # cross compilations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f7bf5ee39c..cb90e3998ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ First of all, thanks for contributing! -This document provides some basic guidelines for contributing to this repository. To propose -improvements or fix a bug, feel free to submit a PR. +This document provides some basic guidelines for contributing to this +repository. To propose improvements or fix a bug, feel free to submit a PR. ## Legal requirements @@ -11,8 +11,8 @@ Before we can accept your contributions you have to sign the [Contributor Licens ## Prerequisites -To build the Arduino CLI from sources you need the following tools to be available in your local -enviroment: +To build the Arduino CLI from sources you need the following tools to be +available in your local enviroment: * [Go][1] version 1.12 or later * [Taskfile][2] to help you run the most common tasks from the command line @@ -30,73 +30,101 @@ From the project folder root, just run: task build ``` -The project uses Go modules so dependencies will be downloaded automatically, you should end with -an `arduino-cli` executable in the same folder. +The project uses Go modules so dependencies will be downloaded automatically; +at the end of the build, you should find an `arduino-cli` executable in the +same folder. ## Running the tests -There are several checks and test suites in place to ensure the code works as expected but also it -is written in a way that's consistent across the whole codebase. Such tests can be run one after -another by running the command: +There are several checks and test suites in place to ensure the code works as +expected but it's also written in a way that's consistent across the whole +codebase. To avoid pushing changes that will cause the CI system to fail, you +can run most of the tests locally. + +To ensure code style is consistent, run: ```shell -task test +task check ``` -If you want to only run unit tests and skip other checks, you can run: +To run unit tests: ```shell task test-unit ``` -Similarly, if you're only interested in running integration tests, you can do (be sure to read the -part dedicated to integration tests if something doesn't work): +To run integration tests (these will take some time and require special setup, +see following paragraph): ```shell task test-integration ``` +### Running only some tests +By default, all tests from all go packages are run. To run only unit +tests from one or more specific packages, you can set the TARGETS +environment variable, e.g.: + + TARGETS=./arduino/cores/packagemanager task test-unit + +Alternatively, to run only some specific test(s), you can specify a regex +to match against the test function name: + + TEST_REGEX='^TestTryBuild.*' task test-unit + +Both can be combined as well, typically to run only a specific test: + + TEST_REGEX='^TestFindBoardWithFQBN$' TARGETS=./arduino/cores/packagemanager task test-unit + +For integration test, the same options are supported. Note that when not +specified, `TEST_REGEX` defaults to "Integration" to select only +integration tests, so if you specify a broader regex, this could cause +non-integration tests to be run as well. ### Integration tests -Being a command line interface, Arduino CLI is heavily interactive and it has to stay consistent -in accepting the user input and providing the expected output and proper exit codes. On top of this, -many Arduino CLI features involve communicating with external devices, most likely through a serial +Being a command line interface, Arduino CLI is heavily interactive and it has to +stay consistent in accepting the user input and providing the expected output +and proper exit codes. On top of this, many Arduino CLI features involve +communicating with external devices, most likely through a serial port, so unit tests can only put our confidence that the code is working so far. -For these reasons, in addition to regular unit tests the project has a suite of integration tests -that actually run Arduino CLI in a different process and assess the options are correctly -understood and the output is what we expect. +For these reasons, in addition to regular unit tests the project has a suite of +integration tests that actually run Arduino CLI in a different process and +assess the options are correctly understood and the output is what we expect. -To run the full suite of integration tests you need an Arduino device attached to a serial port and -a working Python environment. Chances are that you already have Python installed in your system, if -this is not the case you can [download][3] the official distribution or use the package manager -provided by your Operating System. +To run the full suite of integration tests you need an Arduino device attached +to a serial port and a working Python environment. Chances are that you already +have Python installed in your system, if this is not the case you can +[download][3] the official distribution or use the package manager provided by your Operating System. -Some dependencies need to be installed before running the tests and to avoid polluting your global -Python enviroment with dependencies that might be only used by the Arduino CLI, you can use a -[virtual environment][4]. There are many ways to manage virtual environments, for example you can -use a productivity tool called [hatch][5]. First you need to install it (you might need to `sudo` +Some dependencies need to be installed before running the tests and to avoid +polluting your global Python enviroment with dependencies that might be only +used by the Arduino CLI, you can use a [virtual environment][4]. There are many +ways to manage virtual environments, for example you can use a productivity tool +called [hatch][5]. First you need to install it (you might need to `sudo` the following command): ```shell pip3 install --user hatch ``` -Then you can create a virtual environment to be used while working on Arduino CLI: +Then you can create a virtual environment to be used while working on Arduino +CLI: ```shell hatch env arduino-cli ``` -At this point the virtual environment was created and you need to make it active every time you -open a new terminal session with the following command: +At this point the virtual environment was created and you need to make it active +every time you open a new terminal session with the following command: ```shell hatch shell arduino-cli ``` -From now on, every package installed by Python will be confined to the `arduino-cli` virtual -environment, so you can proceed installing the dependencies required with: +From now on, every package installed by Python will be confined to the +`arduino-cli` virtual environment, so you can proceed installing the +dependencies required with: ```shell pip install -r test/requirements.txt @@ -110,29 +138,33 @@ task test-integration ## Pull Requests -In order to ease code reviews and have your contributions merged faster, here is a list of items -you can check before submitting a PR: +In order to ease code reviews and have your contributions merged faster, here is +a list of items you can check before submitting a PR: * Create small PRs that are narrowly focused on addressing a single concern. -* PR titles indirectly become part of the CHANGELOG so it's crucial to provide a good - record of **what** change is being made in the title; **why** it was made will go in the - PR description, along with a link to a GitHub issue if it exists. +* PR titles indirectly become part of the CHANGELOG so it's crucial to provide a + good record of **what** change is being made in the title; **why** it was made + will go in the PR description, along with a link to a GitHub issue if it + exists. * Write tests for the code you wrote. * Open your PR against the `master` branch. * Maintain **clean commit history** and use **meaningful commit messages**. - PRs with messy commit history are difficult to review and require a lot of work to be merged. -* Your PR must pass all CI tests before we will merge it. If you're seeing an error and don't think - it's your fault, it may not be! The reviewer will help you if there are test failures that seem + PRs with messy commit history are difficult to review and require a lot of + work to be merged. +* Your PR must pass all CI tests before we will merge it. If you're seeing an error and don't think + it's your fault, it may not be! The reviewer will help you if there are test + failures that seem not related to the change you are making. ## Additional settings -If you need to push a commit that's only shipping documentation changes or example files, thus a -complete no-op for the test suite, please start the commit message with the string **[skip ci]** -to skip the build and give that slot to someone else who does need it. +If you need to push a commit that's only shipping documentation changes or +example files, thus a complete no-op for the test suite, please start the commit +message with the string **[skip ci]** to skip the build and give that slot to +someone else who does need it. -If your PR doesn't need to be included in the changelog, please start the PR title with the string -**[skip changelog]** +If your PR doesn't need to be included in the changelog, please start the PR +title with the string **[skip changelog]** [0]: https://cla-assistant.io/arduino/arduino-cli [1]: https://golang.org/doc/install diff --git a/Dockerfiles/CI/Dockerfile b/Dockerfiles/CI/Dockerfile deleted file mode 100644 index 55a784a2c81..00000000000 --- a/Dockerfiles/CI/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM golang:1.12 - -RUN apt-get update && apt-get install -y --no-install-recommends \ - bzip2 \ - unzip \ - && rm -rf /var/lib/apt/lists/* - -ENV PROTOBUF_VER 3.9.1 -ENV PATH="/miniconda/bin:${PATH}" - -# NOTE: most of the following assume WORDKIR is '/' -RUN set -ex \ - # Task executor, will be installed in /bin - && curl -sL https://taskfile.dev/install.sh | sh \ - # Codecov uploader - && curl -o /bin/codecov -LO https://codecov.io/bash && chmod +x /bin/codecov \ - # Go runtime dependencies - && go get github.com/golangci/govet \ - && go get golang.org/x/lint/golint \ - # Protobuf tooling - && go get github.com/golang/protobuf/protoc-gen-go \ - && mkdir protobuf && cd protobuf \ - && curl -LO https://github.com/google/protobuf/releases/download/v$PROTOBUF_VER/protoc-$PROTOBUF_VER-linux-x86_64.zip \ - && unzip protoc-$PROTOBUF_VER-linux-x86_64.zip \ - && cp ./bin/* /bin/ \ - # protoc will search for default includes in the path of the binary - && cp -r ./include /bin/ \ - && cd .. && rm -rf protobuf \ - # Install a recent version of Python - && curl -o $HOME/miniconda.sh -LO https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ - && chmod +x $HOME/miniconda.sh && bash ~/miniconda.sh -b -p /miniconda && rm -f $HOME/miniconda.sh \ No newline at end of file diff --git a/Dockerfiles/builder/Dockerfile b/Dockerfiles/builder/Dockerfile index 5cd2eda5018..c8cf088ce6e 100644 --- a/Dockerfiles/builder/Dockerfile +++ b/Dockerfiles/builder/Dockerfile @@ -5,7 +5,7 @@ ENV PATH $PATH:/usr/local/go/bin # Install tooling ENV GORELEASER_VER 0.113.0 ENV GORELEASER_SHA 2379beebb6369b75ccead7f7a43269de700b51821feae3857701d106ed72bd63 -ENV GOVER 1.12.7 +ENV GOVER 1.13 RUN set -ex \ && curl -o goreleaser.tar.gz -LO https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VER}/goreleaser_Linux_x86_64.tar.gz \ && echo "$GORELEASER_SHA goreleaser.tar.gz" | sha256sum -c - || exit 1 \ diff --git a/README.md b/README.md index 1bba27f10fc..0ae46017d8d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # arduino-cli -![Workflow Status](https://github.com/Arduino/arduino-cli/workflows/test/badge.svg) +[![Tests passing](https://github.com/Arduino/arduino-cli/workflows/test/badge.svg)](https://github.com/Arduino/arduino-cli/actions?workflow=test) +[![Nightly build](https://github.com/Arduino/arduino-cli/workflows/nightly/badge.svg)](https://github.com/Arduino/arduino-cli/actions?workflow=nightly) [![codecov](https://codecov.io/gh/arduino/arduino-cli/branch/master/graph/badge.svg)](https://codecov.io/gh/arduino/arduino-cli) `arduino-cli` is an all-in-one solution that provides builder, boards/library manager, uploader, @@ -16,12 +17,28 @@ Contributions are welcome! Please read the document [How to contribute](CONTRIBUTING.md) which will guide you through how to build the source code, run the tests, and contribute your changes to the project. +:sparkles: Thanks to all our [contributors](https://github.com/arduino/arduino-cli/graphs/contributors)! :sparkles: + ## How to install ### Get the latest package -The easiest way to get the latest version of `arduino-cli` on any supported platform is using the -`install.sh` script: +You have several options to install the latest version of the Arduino CLI +on your system. + +#### Install via Homebrew (macOS/Linux) + +The Arduino CLI is available as a Homebrew formula since version `0.5.0`: + +```console +brew update +brew install arduino-cli +``` + +#### Use the install script + +The easiest way to get the latest version of `arduino-cli` on any supported +platform is using the `install.sh` script: ```console curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh @@ -34,18 +51,36 @@ for example `~/local/bin`, set the `BINDIR` environment variable like this: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/local/bin sh ``` +#### Download the latest packages from Arduino CDN + +In order to get the latest stable release for your platform you can use the +following links: + +- [Linux 64 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz) +- [Linux 32 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_32bit.tar.gz) +- [Linux ARM 64 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_ARM64.tar.gz) +- [Linux ARM 32 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_ARMv7.tar.gz) +- [Windows 64 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip) +- [Windows 32 bit](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip) +- [Mac OSX](https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_macOS_64bit.tar.gz) + +These links return a `302: Found` response, redirecting to latest generated builds by replacing `latest` with the latest +available stable release. Once downloaded, place the executable `arduino-cli` into a directory which is in your `PATH`. + **Deprecation notice:** Links in the form `http://downloads.arduino.cc/arduino-cli/arduino-cli-latest-.tar.bz2` won't be further updated. That URL will provide arduino-cli 0.3.7-alpha.preview, regardless of further releases. -Alternatively you can download one of the pre-built binaries for the supported platforms from the -[release page](https://github.com/arduino/arduino-cli/releases). Once downloaded, place the executable -`arduino-cli` into a directory which is in your `PATH`. +#### Download the latest package from the release page on GitHub -### Download a nightly build +Alternatively you can download one of the pre-built binaries for the supported +platforms from the +[release page](https://github.com/arduino/arduino-cli/releases). Once downloaded, +place the executable `arduino-cli` into a directory which is in your `PATH`. -These builds are generated once a day from `master` branch starting at 01:00 GMT +### Get a nightly build -In order to get the latest nightly build for your platform use the following links: +These builds are generated once a day from `master` branch starting at 01:00 GMT. +In order to get the latest nightly build for your platform, use the following links: - [Linux 64 bit](https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-latest_Linux_64bit.tar.gz) - [Linux 32 bit](https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-latest_Linux_32bit.tar.gz) @@ -55,7 +90,7 @@ In order to get the latest nightly build for your platform use the following lin - [Windows 32 bit](https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-latest_Windows_32bit.zip) - [Mac OSX](https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-latest_macOS_64bit.tar.gz) -These links return a `302: Found` response, redirecting to latest generated builds by replacing `latest` with the latest +These links return a `302: Found` response, redirecting to latest generated builds by replacing `latest` with the latest available build date, using the format YYYYMMDD (i.e for 2019/Aug/06 `latest` is replaced with `20190806` ) Checksums for the nightly builds are available at @@ -63,14 +98,14 @@ Checksums for the nightly builds are available at Once downloaded, place the executable `arduino-cli` into a directory which is in your `PATH`. -### Build from source with Docker +### Build from source using Docker -If you don't have a working Golang environment or if you want to build `arduino-cli` targeting -different platforms, you can use Docker to get a binary directly from sources. From the project -folder run: +If you don't have a working Golang environment or if you want to build +`arduino-cli` targeting different platforms, you can use Docker to get a binary +directly from sources. From the project folder run: ```console -docker run -v $PWD:/arduino-cli -w /arduino-cli arduino/arduino-cli:builder-0.1 goreleaser --rm-dist --snapshot --skip-publish +docker run -v $PWD:/arduino-cli -w /arduino-cli -e PACKAGE_NAME_PREFIX='snapshot' arduino/arduino-cli:builder-1 goreleaser --rm-dist --snapshot --skip-publish ``` Once the build is over, you will find a `./dist/` folder containing the packages built out of @@ -90,13 +125,13 @@ IDE has without the graphical UI. ### Step 1. Create a new sketch -The command will create a new empty sketch named MyFirstSketch in the default directory under \$HOME/Arduino/ +The command will create a new empty sketch named `MyFirstSketch` in the current directory ```console $ arduino-cli sketch new MyFirstSketch -Sketch created in: /home/luca/Arduino/MyFirstSketch +Sketch created in: /home/luca/MyFirstSketch -$ cat /home/luca/Arduino/MyFirstSketch/MyFirstSketch.ino +$ cat /home/luca/MyFirstSketch/MyFirstSketch.ino void setup() { } @@ -106,7 +141,8 @@ void loop() { ### Step 2. Modify your sketch -Use your favourite file editor or IDE to modify the .ino file under: `$HOME/Arduino/MyFirstSketch/MyFirstSketch.ino` +Use your favourite file editor or IDE to modify the .ino file, in this example +under: `$HOME/MyFirstSketch/MyFirstSketch.ino` and change the file to look like this one: ```C @@ -132,49 +168,22 @@ $ arduino-cli core update-index Updating index: package_index.json downloaded ``` -Now, just connect the board to your PCs by using the USB cable. In this example we will use the +Now, just connect the board to your PCs by using the USB cable. (**Note**: Connecting through an FTDI adapter chip will show Unknown for the Board Name because the VID/PID is generic. Uploading should still work as long as you identify the correct FQBN). In this example we will use the MKR1000 board: ```console $ arduino-cli board list -FQBN Port ID Board Name - /dev/ttyACM0 2341:804E unknown +Port Type Board Name FQBN Core +/dev/ttyACM1 Serial Port (USB) Arduino/Genuino MKR1000 arduino:samd:mkr1000 arduino:samd ``` -the board has been discovered but we do not have the correct core to program it yet. -Let's install it! +the board has been discovered but we need the correct core to program it, let's +install it! -### Step 4. Find and install the right core +### Step 4. Install the core for your board -We have to look at the core available with the `core search` command. It will provide a list of -available cores matching the name arduino: - -```console -$ arduino-cli core search arduino -Searching for platforms matching 'arduino' -ID Version Name -Intel:arc32 2.0.4 Intel Curie Boards -arduino:avr 1.6.23 Arduino AVR Boards -arduino:mbed 1.1.0 Arduino nRF528x Boards (Mbed OS) -arduino:megaavr 1.8.3 Arduino megaAVR Boards -arduino:nrf52 1.0.2 Arduino nRF52 Boards -arduino:sam 1.6.12 Arduino SAM Boards (32-bits ARM Cortex-M3) -arduino:samd 1.8.3 Arduino SAMD Boards (32-bits ARM Cortex-M0+) -arduino:samd_beta 1.6.25 Arduino SAMD Beta Boards (32-bits ARM Cortex-M0+) -arduino:stm32f4 1.0.1 Arduino STM32F4 Boards -littleBits:avr 1.0.0 littleBits Arduino AVR Modules -``` - -If you're unsure you can try to refine the search with the board name - -```console -$ arduino-cli core search mkr1000 -Searching for platforms matching 'mkr1000' -ID Version Name -arduino:samd 1.8.3 Arduino SAMD Boards (32-bits ARM Cortex-M0+) -``` - -So, the right platform for the Arduino MKR1000 is arduino:samd, now we can install it +From the output of the `board list` command, the right platform for the Arduino +MKR1000 is `arduino:samd`, we can install it with: ```console $ arduino-cli core install arduino:samd @@ -207,14 +216,6 @@ ID Installed Latest Name arduino:samd 1.6.19 1.6.19 Arduino SAMD Boards (32-bits ARM Cortex-M0+) ``` -We can finally check if the board is now recognized as a MKR1000 - -```console -$ arduino-cli board list -FQBN Port ID Board Name -arduino:samd:mkr1000 /dev/ttyACM0 2341:804E Arduino/Genuino MKR1000 -``` - If the board is not detected for any reason, you can list all the supported boards with `arduino-cli board listall` and also search for a specific board: @@ -229,8 +230,7 @@ Arduino MKRZERO arduino:samd:mkrzero Arduino/Genuino MKR1000 arduino:samd:mkr1000 ``` -Great! Now we have the Board FQBN (Fully Qualified Board Name) `arduino:samd:mkr1000` -and the Board Name look good, we are ready to compile and upload the sketch +Great! Now we are ready to compile and upload the sketch. #### Adding 3rd party cores @@ -273,7 +273,7 @@ To compile the sketch we have to run the `compile` command with the proper FQBN previous command. ```console -$ arduino-cli compile --fqbn arduino:samd:mkr1000 Arduino/MyFirstSketch +$ arduino-cli compile --fqbn arduino:samd:mkr1000 MyFirstSketch Sketch uses 9600 bytes (3%) of program storage space. Maximum is 262144 bytes. ``` @@ -283,7 +283,7 @@ We can finally upload the sketch and see our board blinking, we now have to spec used by our board other than the FQBN: ```console -$ arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:samd:mkr1000 Arduino/MyFirstSketch +$ arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:samd:mkr1000 MyFirstSketch No new serial port detected. Atmel SMART device 0x10010005 found Device : ATSAMD21G18A diff --git a/Taskfile.yml b/Taskfile.yml index d8b30149aa2..325172222d5 100755 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -21,12 +21,12 @@ tasks: test-unit: desc: Run unit tests only cmds: - - go test -short {{ default "-v" .GOFLAGS }} -coverprofile=coverage_unit.txt {{ default .DEFAULT_TARGETS .TARGETS }} {{.TEST_LDFLAGS}} + - go test -short -run '{{ default ".*" .TEST_REGEX }}' {{ default "-v" .GOFLAGS }} -coverprofile=coverage_unit.txt {{ default .DEFAULT_TARGETS .TARGETS }} {{.TEST_LDFLAGS}} test-integration: desc: Run integration tests only cmds: - - go test -run Integration {{ default "-v" .GOFLAGS }} -coverprofile=coverage_integ.txt {{ default .DEFAULT_TARGETS .TARGETS }} {{.TEST_LDFLAGS}} + - go test -run '{{ default "Integration" .TEST_REGEX }}' {{ default "-v" .GOFLAGS }} -coverprofile=coverage_integ.txt {{ default .DEFAULT_TARGETS .TARGETS }} {{.TEST_LDFLAGS}} - pytest test test-legacy: @@ -63,12 +63,10 @@ vars: sh: echo `go list ./... | grep -v legacy | tr '\n' ' '` # build vars - VERSIONSTRING: "0.3.7-alpha.preview" COMMIT: sh: echo ${TRAVIS_COMMIT:-`git log -n 1 --format=%h`} LDFLAGS: > - -ldflags '-X github.com/arduino/arduino-cli/version.versionString={{.VERSIONSTRING}} - -X github.com/arduino/arduino-cli/version.commit={{.COMMIT}}' + -ldflags '-X github.com/arduino/arduino-cli/version.commit={{.COMMIT}}' # test vars GOFLAGS: "-timeout 10m -v -coverpkg=./... -covermode=atomic" diff --git a/arduino/builder/builder.go b/arduino/builder/builder.go index 02f264c383a..59e3ff8a02e 100644 --- a/arduino/builder/builder.go +++ b/arduino/builder/builder.go @@ -19,18 +19,22 @@ import ( "crypto/md5" "encoding/hex" "os" - "path/filepath" "strings" + "github.com/arduino/go-paths-helper" "github.com/pkg/errors" ) -// GenBuildPath generates a suitable name for the build folder -func GenBuildPath(sketchPath string) string { - md5SumBytes := md5.Sum([]byte(sketchPath)) +// GenBuildPath generates a suitable name for the build folder. +// The sketchPath, if not nil, is also used to furhter differentiate build paths. +func GenBuildPath(sketchPath *paths.Path) *paths.Path { + path := "" + if sketchPath != nil { + path = sketchPath.String() + } + md5SumBytes := md5.Sum([]byte(path)) md5Sum := strings.ToUpper(hex.EncodeToString(md5SumBytes[:])) - - return filepath.Join(os.TempDir(), "arduino-sketch-"+md5Sum) + return paths.TempDir().Join("arduino-sketch-" + md5Sum) } // EnsureBuildPathExists creates the build path if doesn't already exists. diff --git a/arduino/builder/builder_test.go b/arduino/builder/builder_test.go index 2048f75876d..f172ff9f469 100644 --- a/arduino/builder/builder_test.go +++ b/arduino/builder/builder_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/arduino/arduino-cli/arduino/builder" + "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/assert" ) @@ -36,7 +37,10 @@ func tmpDirOrDie() string { func TestGenBuildPath(t *testing.T) { want := filepath.Join(os.TempDir(), "arduino-sketch-ACBD18DB4CC2F85CEDEF654FCCC4A4D8") - assert.Equal(t, want, builder.GenBuildPath("foo")) + assert.Equal(t, want, builder.GenBuildPath(paths.New("foo")).String()) + + want = filepath.Join(os.TempDir(), "arduino-sketch-D41D8CD98F00B204E9800998ECF8427E") + assert.Equal(t, want, builder.GenBuildPath(nil).String()) } func TestEnsureBuildPathExists(t *testing.T) { diff --git a/arduino/builder/sketch.go b/arduino/builder/sketch.go index 99cd3b368a4..d9ad4c50385 100644 --- a/arduino/builder/sketch.go +++ b/arduino/builder/sketch.go @@ -25,10 +25,16 @@ import ( "github.com/arduino/arduino-cli/arduino/globals" "github.com/arduino/arduino-cli/arduino/sketch" + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" "github.com/pkg/errors" ) +// As currently implemented on Linux, +// the maximum number of symbolic links that will be followed while resolving a pathname is 40 +const maxFileSystemDepth = 40 + var includesArduinoH = regexp.MustCompile(`(?m)^\s*#\s*include\s*[<\"]Arduino\.h[>\"]`) // QuoteCppString returns the given string as a quoted string for use with the C @@ -58,6 +64,39 @@ func SketchSaveItemCpp(item *sketch.Item, destPath string) error { return nil } +// simpleLocalWalk locally replaces filepath.Walk and/but goes through symlinks +func simpleLocalWalk(root string, maxDepth int, walkFn func(path string, info os.FileInfo, err error) error) error { + + info, err := os.Stat(root) + + if err != nil { + return walkFn(root, nil, err) + } + + err = walkFn(root, info, err) + if err == filepath.SkipDir { + return nil + } + + if info.IsDir() { + if maxDepth <= 0 { + return walkFn(root, info, errors.New("Filesystem bottom is too deep (directory recursion or filesystem really deep): "+root)) + } + maxDepth-- + files, err := ioutil.ReadDir(root) + if err == nil { + for _, file := range files { + err = simpleLocalWalk(root+string(os.PathSeparator)+file.Name(), maxDepth, walkFn) + if err == filepath.SkipDir { + return nil + } + } + } + } + + return nil +} + // SketchLoad collects all the files composing a sketch. // The parameter `sketchPath` holds a path pointing to a single sketch file or a sketch folder, // the path must be absolute. @@ -79,6 +118,14 @@ func SketchLoad(sketchPath, buildPath string) (*sketch.Sketch, error) { return nil, errors.Wrap(err, "unable to find the main sketch file") } f.Close() + // ensure it is not a directory + info, err := os.Stat(mainSketchFile) + if err != nil { + return nil, errors.Wrap(err, "unable to check the main sketch file") + } + if info.IsDir() { + return nil, errors.Wrap(errors.New(mainSketchFile), "sketch must not be a directory") + } } else { sketchFolder = filepath.Dir(sketchPath) mainSketchFile = sketchPath @@ -86,7 +133,13 @@ func SketchLoad(sketchPath, buildPath string) (*sketch.Sketch, error) { // collect all the sketch files var files []string - err = filepath.Walk(sketchFolder, func(path string, info os.FileInfo, err error) error { + err = simpleLocalWalk(sketchFolder, maxFileSystemDepth, func(path string, info os.FileInfo, err error) error { + + if err != nil { + feedback.Errorf("Error during sketch processing: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + // ignore hidden files and skip hidden directories if strings.HasPrefix(info.Name(), ".") { if info.IsDir() { diff --git a/arduino/builder/sketch_test.go b/arduino/builder/sketch_test.go index e6526f6fbc8..409e258d541 100644 --- a/arduino/builder/sketch_test.go +++ b/arduino/builder/sketch_test.go @@ -82,6 +82,48 @@ func TestLoadSketchFolder(t *testing.T) { require.Equal(t, "helper.h", filepath.Base(s.AdditionalFiles[2].Path)) } +func TestLoadSketchFolderSymlink(t *testing.T) { + // pass the path to the sketch folder + symlinkSketchPath := filepath.Join("testdata", t.Name()) + srcSketchPath := t.Name() + "Src" + os.Symlink(srcSketchPath, symlinkSketchPath) + mainFilePath := filepath.Join(symlinkSketchPath, t.Name()+".ino") + s, err := builder.SketchLoad(symlinkSketchPath, "") + require.Nil(t, err) + require.NotNil(t, s) + require.Equal(t, mainFilePath, s.MainFile.Path) + require.Equal(t, symlinkSketchPath, s.LocationPath) + require.Len(t, s.OtherSketchFiles, 2) + require.Equal(t, "old.pde", filepath.Base(s.OtherSketchFiles[0].Path)) + require.Equal(t, "other.ino", filepath.Base(s.OtherSketchFiles[1].Path)) + require.Len(t, s.AdditionalFiles, 3) + require.Equal(t, "header.h", filepath.Base(s.AdditionalFiles[0].Path)) + require.Equal(t, "s_file.S", filepath.Base(s.AdditionalFiles[1].Path)) + require.Equal(t, "helper.h", filepath.Base(s.AdditionalFiles[2].Path)) + + // pass the path to the main file + symlinkSketchPath = mainFilePath + s, err = builder.SketchLoad(symlinkSketchPath, "") + require.Nil(t, err) + require.NotNil(t, s) + require.Equal(t, mainFilePath, s.MainFile.Path) + require.Len(t, s.OtherSketchFiles, 2) + require.Equal(t, "old.pde", filepath.Base(s.OtherSketchFiles[0].Path)) + require.Equal(t, "other.ino", filepath.Base(s.OtherSketchFiles[1].Path)) + require.Len(t, s.AdditionalFiles, 3) + require.Equal(t, "header.h", filepath.Base(s.AdditionalFiles[0].Path)) + require.Equal(t, "s_file.S", filepath.Base(s.AdditionalFiles[1].Path)) + require.Equal(t, "helper.h", filepath.Base(s.AdditionalFiles[2].Path)) +} + +func TestLoadSketchFolderIno(t *testing.T) { + // pass the path to the sketch folder + sketchPath := filepath.Join("testdata", t.Name()) + _, err := builder.SketchLoad(sketchPath, "") + require.Error(t, err) + require.Contains(t, err.Error(), "sketch must not be a directory") +} + func TestLoadSketchFolderWrongMain(t *testing.T) { sketchPath := filepath.Join("testdata", t.Name()) _, err := builder.SketchLoad(sketchPath, "") diff --git a/arduino/builder/testdata/TestLoadSketchFolderIno/TestLoadSketchFolderIno.ino/TestLoadSketchFolder.ino b/arduino/builder/testdata/TestLoadSketchFolderIno/TestLoadSketchFolderIno.ino/TestLoadSketchFolder.ino new file mode 100644 index 00000000000..0d5e0f5ceb9 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderIno/TestLoadSketchFolderIno.ino/TestLoadSketchFolder.ino @@ -0,0 +1,7 @@ +void setup() { + +} + +void loop() { + +} \ No newline at end of file diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/.#sketch.ino b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/.#sketch.ino new file mode 100644 index 00000000000..71048175432 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/.#sketch.ino @@ -0,0 +1,2 @@ +void setup() +void loop) } \ No newline at end of file diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/TestLoadSketchFolderSymlink.ino b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/TestLoadSketchFolderSymlink.ino new file mode 100644 index 00000000000..0d5e0f5ceb9 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/TestLoadSketchFolderSymlink.ino @@ -0,0 +1,7 @@ +void setup() { + +} + +void loop() { + +} \ No newline at end of file diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/doc.txt b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/doc.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/header.h b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/header.h new file mode 100644 index 00000000000..0e7d3b1a6a9 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/header.h @@ -0,0 +1 @@ +#define FOO "BAR" \ No newline at end of file diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/old.pde b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/old.pde new file mode 100644 index 00000000000..e69de29bb2d diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/other.ino b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/other.ino new file mode 100644 index 00000000000..c426196c017 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/other.ino @@ -0,0 +1,3 @@ +String hello() { + return "world"; +} \ No newline at end of file diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/s_file.S b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/s_file.S new file mode 100644 index 00000000000..e69de29bb2d diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/src/dont_load_me.ino b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/src/dont_load_me.ino new file mode 100644 index 00000000000..46b07018d09 --- /dev/null +++ b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/src/dont_load_me.ino @@ -0,0 +1,2 @@ +#include +#error "Whattya looking at?" diff --git a/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/src/helper.h b/arduino/builder/testdata/TestLoadSketchFolderSymlinkSrc/src/helper.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/arduino/cores/packagemanager/identify.go b/arduino/cores/packagemanager/identify.go index 926fa4dd1bf..16e9bed3b89 100644 --- a/arduino/cores/packagemanager/identify.go +++ b/arduino/cores/packagemanager/identify.go @@ -30,7 +30,7 @@ func (pm *PackageManager) IdentifyBoard(idProps *properties.Map) []*cores.Board return []*cores.Board{} } - checkSuffix := func(props *properties.Map, s string) (checked bool, found bool) { + checkSuffix := func(props *properties.Map, s string) (present bool, matched bool) { for k, v1 := range idProps.AsMap() { v2, ok := props.GetOk(k + s) if !ok { @@ -45,17 +45,17 @@ func (pm *PackageManager) IdentifyBoard(idProps *properties.Map) []*cores.Board foundBoards := []*cores.Board{} for _, board := range pm.InstalledBoards() { - if _, found := checkSuffix(board.Properties, ""); found { + if _, matched := checkSuffix(board.Properties, ""); matched { foundBoards = append(foundBoards, board) continue } id := 0 for { - again, found := checkSuffix(board.Properties, fmt.Sprintf(".%d", id)) - if found { + present, matched := checkSuffix(board.Properties, fmt.Sprintf(".%d", id)) + if matched { foundBoards = append(foundBoards, board) } - if !again { + if !present && id > 0 { // Always check id 0 and 1 (https://github.com/arduino/arduino-cli/issues/456) break } id++ diff --git a/arduino/cores/packagemanager/loader.go b/arduino/cores/packagemanager/loader.go index 8a542ef4a03..f0c2f99d548 100644 --- a/arduino/cores/packagemanager/loader.go +++ b/arduino/cores/packagemanager/loader.go @@ -77,6 +77,17 @@ func (pm *PackageManager) LoadHardwareFromDirectory(path *paths.Path) error { for _, packagerPath := range files { packager := packagerPath.Base() + // Load custom platform properties if available + if packager == "platform.txt" { + pm.Log.Infof("Loading custom platform properties: %s", packagerPath) + if p, err := properties.LoadFromPath(packagerPath); err != nil { + pm.Log.WithError(err).Errorf("Error loading properties.") + } else { + pm.CustomGlobalProperties.Merge(p) + } + continue + } + // First exclude all "tools" directory if packager == "tools" { pm.Log.Infof("Excluding directory: %s", packagerPath) diff --git a/arduino/cores/packagemanager/package_manager.go b/arduino/cores/packagemanager/package_manager.go index 44427537025..0eb7727577a 100644 --- a/arduino/cores/packagemanager/package_manager.go +++ b/arduino/cores/packagemanager/package_manager.go @@ -38,23 +38,25 @@ import ( // The manager also keeps track of the status of the Packages (their Platform Releases, actually) // installed in the system. type PackageManager struct { - Log logrus.FieldLogger - Packages cores.Packages - IndexDir *paths.Path - PackagesDir *paths.Path - DownloadDir *paths.Path - TempDir *paths.Path + Log logrus.FieldLogger + Packages cores.Packages + IndexDir *paths.Path + PackagesDir *paths.Path + DownloadDir *paths.Path + TempDir *paths.Path + CustomGlobalProperties *properties.Map } // NewPackageManager returns a new instance of the PackageManager func NewPackageManager(indexDir, packagesDir, downloadDir, tempDir *paths.Path) *PackageManager { return &PackageManager{ - Log: logrus.StandardLogger(), - Packages: cores.NewPackages(), - IndexDir: indexDir, - PackagesDir: packagesDir, - DownloadDir: downloadDir, - TempDir: tempDir, + Log: logrus.StandardLogger(), + Packages: cores.NewPackages(), + IndexDir: indexDir, + PackagesDir: packagesDir, + DownloadDir: downloadDir, + TempDir: tempDir, + CustomGlobalProperties: properties.NewMap(), } } @@ -177,13 +179,17 @@ func (pm *PackageManager) ResolveFQBN(fqbn *cores.FQBN) ( buildPlatformRelease := platformRelease coreParts := strings.Split(buildProperties.Get("build.core"), ":") if len(coreParts) > 1 { - referredPackage := coreParts[1] + referredPackage := coreParts[0] buildPackage := pm.Packages[referredPackage] if buildPackage == nil { return targetPackage, platformRelease, board, buildProperties, nil, - fmt.Errorf("missing package %s:%s required for build", referredPackage, platform) + fmt.Errorf("missing package %s referenced by board %s", referredPackage, fqbn) } buildPlatform := buildPackage.Platforms[fqbn.PlatformArch] + if buildPlatform == nil { + return targetPackage, platformRelease, board, buildProperties, nil, + fmt.Errorf("missing platform %s:%s referenced by board %s", referredPackage, fqbn.PlatformArch, fqbn) + } buildPlatformRelease = pm.GetInstalledPlatformRelease(buildPlatform) } diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go index 1dadc096a0d..94ffdf5c012 100644 --- a/arduino/cores/packagemanager/package_manager_test.go +++ b/arduino/cores/packagemanager/package_manager_test.go @@ -18,6 +18,7 @@ package packagemanager_test import ( + "fmt" "net/url" "testing" @@ -33,6 +34,9 @@ import ( var customHardware = paths.New("testdata", "custom_hardware") var dataDir1 = paths.New("testdata", "data_dir_1") +// Intended to be used alongside dataDir1 +var extraHardware = paths.New("testdata", "extra_hardware") + func TestFindBoardWithFQBN(t *testing.T) { pm := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware) pm.LoadHardwareFromDirectory(customHardware) @@ -48,6 +52,128 @@ func TestFindBoardWithFQBN(t *testing.T) { require.Equal(t, board.Name(), "Arduino/Genuino Mega or Mega 2560") } +func TestResolveFQBN(t *testing.T) { + // Pass nil, since these paths are only used for installing + pm := packagemanager.NewPackageManager(nil, nil, nil, nil) + // Hardware from main packages directory + pm.LoadHardwareFromDirectory(dataDir1.Join("packages")) + // This contains the arduino:avr core + pm.LoadHardwareFromDirectory(customHardware) + // This contains the referenced:avr core + pm.LoadHardwareFromDirectory(extraHardware) + + fqbn, err := cores.ParseFQBN("arduino:avr:uno") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err := pm.ResolveFQBN(fqbn) + require.Nil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "arduino:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Arduino/Genuino Uno") + require.NotNil(t, props) + require.Equal(t, platformRelease, buildPlatformRelease) + + fqbn, err = cores.ParseFQBN("arduino:avr:mega") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.Nil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "arduino:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Arduino/Genuino Mega or Mega 2560") + require.NotNil(t, props) + require.Equal(t, platformRelease, buildPlatformRelease) + + // Test a board referenced from the main AVR arduino platform + fqbn, err = cores.ParseFQBN("referenced:avr:uno") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.Nil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "referenced:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Referenced Uno") + require.NotNil(t, props) + require.NotNil(t, buildPlatformRelease) + require.NotNil(t, buildPlatformRelease.Platform) + require.Equal(t, buildPlatformRelease.Platform.String(), "arduino:avr") + + // Test a board referenced from the Adafruit SAMD core (this tests + // deriving where the package and core name are different) + fqbn, err = cores.ParseFQBN("referenced:samd:feather_m0") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.Nil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "referenced:samd") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Referenced Feather M0") + require.NotNil(t, props) + require.NotNil(t, buildPlatformRelease) + require.NotNil(t, buildPlatformRelease.Platform) + require.Equal(t, buildPlatformRelease.Platform.String(), "adafruit:samd") + + // Test a board referenced from a non-existent package + fqbn, err = cores.ParseFQBN("referenced:avr:dummy_invalid_package") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.NotNil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "referenced:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Referenced dummy with invalid package") + require.NotNil(t, props) + require.Nil(t, buildPlatformRelease) + + // Test a board referenced from a non-existent platform/architecture + fqbn, err = cores.ParseFQBN("referenced:avr:dummy_invalid_platform") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.NotNil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "referenced:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Referenced dummy with invalid platform") + require.NotNil(t, props) + require.Nil(t, buildPlatformRelease) + + // Test a board referenced from a non-existent core + // Note that ResolveFQBN does not actually check this currently + fqbn, err = cores.ParseFQBN("referenced:avr:dummy_invalid_core") + require.Nil(t, err) + require.NotNil(t, fqbn) + pkg, platformRelease, board, props, buildPlatformRelease, err = pm.ResolveFQBN(fqbn) + require.Nil(t, err) + require.Equal(t, pkg, platformRelease.Platform.Package) + require.NotNil(t, platformRelease) + require.NotNil(t, platformRelease.Platform) + require.Equal(t, platformRelease.Platform.String(), "referenced:avr") + require.NotNil(t, board) + require.Equal(t, board.Name(), "Referenced dummy with invalid core") + require.NotNil(t, props) + require.NotNil(t, buildPlatformRelease) + require.NotNil(t, buildPlatformRelease.Platform) + require.Equal(t, buildPlatformRelease.Platform.String(), "arduino:avr") +} + func TestBoardOptionsFunctions(t *testing.T) { pm := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware) pm.LoadHardwareFromDirectory(customHardware) @@ -170,3 +296,23 @@ func TestFindToolsRequiredForBoard(t *testing.T) { } require.Equal(t, bossac18.InstallDir.String(), uploadProperties.Get("runtime.tools.bossac.path")) } + +func TestIdentifyBoard(t *testing.T) { + pm := packagemanager.NewPackageManager(customHardware, customHardware, customHardware, customHardware) + pm.LoadHardwareFromDirectory(customHardware) + + identify := func(vid, pid string) []*cores.Board { + return pm.IdentifyBoard(properties.NewFromHashmap(map[string]string{ + "vid": vid, "pid": pid, + })) + } + require.Equal(t, "[arduino:avr:uno]", fmt.Sprintf("%v", identify("0x2341", "0x0001"))) + + // Check indexed vid/pid format (vid.0/pid.0) + require.Equal(t, "[test:avr:a]", fmt.Sprintf("%v", identify("0x9999", "0x0001"))) + require.Equal(t, "[test:avr:b]", fmt.Sprintf("%v", identify("0x9999", "0x0002"))) + require.Equal(t, "[test:avr:c]", fmt.Sprintf("%v", identify("0x9999", "0x0003"))) + require.Equal(t, "[test:avr:c]", fmt.Sprintf("%v", identify("0x9999", "0x0004"))) + // https://github.com/arduino/arduino-cli/issues/456 + require.Equal(t, "[test:avr:d]", fmt.Sprintf("%v", identify("0x9999", "0x0005"))) +} diff --git a/arduino/cores/packagemanager/testdata/custom_hardware/test/avr/boards.txt b/arduino/cores/packagemanager/testdata/custom_hardware/test/avr/boards.txt new file mode 100644 index 00000000000..76a3f32baf2 --- /dev/null +++ b/arduino/cores/packagemanager/testdata/custom_hardware/test/avr/boards.txt @@ -0,0 +1,17 @@ +a.name=Board A +a.vid=0x9999 +a.pid=0x0001 + +b.name=Board B +b.vid.0=0x9999 +b.pid.0=0x0002 + +c.name=Board C +c.vid.0=0x9999 +c.pid.0=0x0003 +c.vid.1=0x9999 +c.pid.1=0x0004 + +d.name=Board D +d.vid.1=0x9999 +d.pid.1=0x0005 diff --git a/arduino/cores/packagemanager/testdata/extra_hardware/referenced/avr/boards.txt b/arduino/cores/packagemanager/testdata/extra_hardware/referenced/avr/boards.txt new file mode 100644 index 00000000000..a242ab52d1b --- /dev/null +++ b/arduino/cores/packagemanager/testdata/extra_hardware/referenced/avr/boards.txt @@ -0,0 +1,38 @@ +# Dummy board that is pretty much identical to the Uno, but defined in a +# different package (to test using a core from a different package where +# the package name and core name are the same). +uno.name=Referenced Uno + +uno.upload.tool=avrdude +uno.upload.protocol=arduino +uno.upload.maximum_size=32256 +uno.upload.maximum_data_size=2048 +uno.upload.speed=115200 + +uno.bootloader.tool=avrdude +uno.bootloader.low_fuses=0xFF +uno.bootloader.high_fuses=0xDE +uno.bootloader.extended_fuses=0xFD +uno.bootloader.unlock_bits=0x3F +uno.bootloader.lock_bits=0x0F +uno.bootloader.file=optiboot/optiboot_atmega328.hex + +uno.build.mcu=atmega328p +uno.build.f_cpu=16000000L +uno.build.board=AVR_UNO +uno.build.core=arduino:arduino +uno.build.variant=standard + +# Dummy board derived from a non-existent package +dummy_invalid_package.name=Referenced dummy with invalid package +dummy_invalid_package.build.core=nonexistent:arduino + +# Dummy board derived from a non-existent core +dummy_invalid_core.name=Referenced dummy with invalid core +dummy_invalid_core.build.core=arduino:nonexistent + +# Dummy board derived from a non-existent platform/architecture. The +# platform is avr, which is implied by the directory this file is in. The +# adafruit package in this test data only supplies a samd platform. +dummy_invalid_platform.name=Referenced dummy with invalid platform +dummy_invalid_platform.build.core=adafruit:arduino diff --git a/arduino/cores/packagemanager/testdata/extra_hardware/referenced/samd/boards.txt b/arduino/cores/packagemanager/testdata/extra_hardware/referenced/samd/boards.txt new file mode 100644 index 00000000000..e988fc204c9 --- /dev/null +++ b/arduino/cores/packagemanager/testdata/extra_hardware/referenced/samd/boards.txt @@ -0,0 +1,27 @@ +# Dummy board that is pretty much identical to the feather m0, but +# defined in a different package (to test using a core from a different +# package where the package name and core name are different). +feather_m0.name=Referenced Feather M0 +feather_m0.upload.tool=bossac +feather_m0.upload.protocol=sam-ba +feather_m0.upload.maximum_size=262144 +feather_m0.upload.offset=0x2000 +feather_m0.upload.use_1200bps_touch=true +feather_m0.upload.wait_for_upload_port=true +feather_m0.upload.native_usb=true +feather_m0.build.mcu=cortex-m0plus +feather_m0.build.f_cpu=48000000L +feather_m0.build.usb_product="Feather M0" +feather_m0.build.usb_manufacturer="Adafruit" +feather_m0.build.board=SAMD_ZERO +feather_m0.build.core=adafruit:arduino +feather_m0.build.extra_flags=-DARDUINO_SAMD_ZERO -DARM_MATH_CM0PLUS -DADAFRUIT_FEATHER_M0 -D__SAMD21G18A__ {build.usb_flags} +feather_m0.build.ldscript=linker_scripts/gcc/flash_with_bootloader.ld +feather_m0.build.openocdscript=openocd_scripts/feather_m0.cfg +feather_m0.build.variant=feather_m0 +feather_m0.build.variant_system_lib= +feather_m0.build.vid=0x239A +feather_m0.build.pid=0x800B +feather_m0.bootloader.tool=openocd +feather_m0.bootloader.file=featherM0/bootloader-feather_m0-v2.0.0-adafruit.5.bin + diff --git a/arduino/libraries/librarieslist.go b/arduino/libraries/librarieslist.go index bc135253a34..34bfc4d5826 100644 --- a/arduino/libraries/librarieslist.go +++ b/arduino/libraries/librarieslist.go @@ -61,6 +61,14 @@ func (list *List) SortByArchitecturePriority(arch string) { }) } +// SortByName sorts the libraries by name +func (list *List) SortByName() { + sort.Slice(*list, func(i, j int) bool { + a, b := (*list)[i], (*list)[j] + return a.Name < b.Name + }) +} + /* // HasHigherPriority returns true if library x has higher priority compared to library // y for the given header and architecture. diff --git a/arduino/libraries/librariesresolver/cpp.go b/arduino/libraries/librariesresolver/cpp.go index a932f59444f..6870959fb04 100644 --- a/arduino/libraries/librariesresolver/cpp.go +++ b/arduino/libraries/librariesresolver/cpp.go @@ -105,11 +105,14 @@ func (resolver *Cpp) ResolveFor(header, architecture string) *libraries.Library // If more than one library qualifies use the "closestmatch" algorithm to // find the best matching one (instead of choosing it randomly) - winner := findLibraryWithNameBestDistance(header, found) - if winner != nil { - logrus.WithField("lib", winner.Name).Info(" library with the best mathing name") + if best := findLibraryWithNameBestDistance(header, found); best != nil { + logrus.WithField("lib", best.Name).Info(" library with the best matching name") + return best } - return winner + + found.SortByName() + logrus.WithField("lib", found[0].Name).Info(" first library in alphabetic order") + return found[0] } func simplify(name string) string { diff --git a/arduino/libraries/librariesresolver/cpp_test.go b/arduino/libraries/librariesresolver/cpp_test.go index fe6dc6f49c8..2d89634a08a 100644 --- a/arduino/libraries/librariesresolver/cpp_test.go +++ b/arduino/libraries/librariesresolver/cpp_test.go @@ -32,6 +32,18 @@ var l5 = &libraries.Library{Name: "Yet Another Calculus Lib Improved", Location: var l6 = &libraries.Library{Name: "Calculus Unified Lib", Location: libraries.Sketchbook} var l7 = &libraries.Library{Name: "AnotherLib", Location: libraries.Sketchbook} +func TestClosestMatchWithTotallyDifferentNames(t *testing.T) { + libraryList := libraries.List{} + libraryList.Add(l5) + libraryList.Add(l6) + libraryList.Add(l7) + resolver := NewCppResolver() + resolver.headers["XYZ.h"] = libraryList + res := resolver.ResolveFor("XYZ.h", "xyz") + require.NotNil(t, res) + require.Equal(t, l7, res, "selected library") +} + func TestCppHeaderPriority(t *testing.T) { r1 := computePriority(l1, "calculus_lib.h", "avr") r2 := computePriority(l2, "calculus_lib.h", "avr") diff --git a/cli/board/attach.go b/cli/board/attach.go index 80b1e68bd9f..76b611d1183 100644 --- a/cli/board/attach.go +++ b/cli/board/attach.go @@ -27,6 +27,8 @@ import ( "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" + "github.com/arduino/go-paths-helper" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -52,14 +54,18 @@ var attachFlags struct { func runAttachCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstance() - var path string - if len(args) > 0 { - path = args[1] + + var path *paths.Path + if len(args) > 1 { + path = paths.New(args[1]) + } else { + path = initSketchPath(path) } + _, err := board.Attach(context.Background(), &rpc.BoardAttachReq{ Instance: instance, BoardUri: args[0], - SketchPath: path, + SketchPath: path.String(), SearchTimeout: attachFlags.searchTimeout, }, output.TaskProgress()) if err != nil { @@ -67,3 +73,18 @@ func runAttachCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } } + +// initSketchPath returns the current working directory +func initSketchPath(sketchPath *paths.Path) *paths.Path { + if sketchPath != nil { + return sketchPath + } + + wd, err := paths.Getwd() + if err != nil { + feedback.Errorf("Couldn't get current working directory: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + logrus.Infof("Reading sketch from dir: %s", wd) + return wd +} diff --git a/cli/board/details.go b/cli/board/details.go index 40ff23aeeb8..f34e9b7a6db 100644 --- a/cli/board/details.go +++ b/cli/board/details.go @@ -54,7 +54,7 @@ func runDetailsCommand(cmd *cobra.Command, args []string) { feedback.PrintResult(detailsResult{details: res}) } -// ouput from this command requires special formatting, let's create a dedicated +// output from this command requires special formatting, let's create a dedicated // feedback.Result implementation type detailsResult struct { details *rpc.BoardDetailsResp diff --git a/cli/board/list.go b/cli/board/list.go index 5a70e69687e..a8f73929cf8 100644 --- a/cli/board/list.go +++ b/cli/board/list.go @@ -18,13 +18,14 @@ package board import ( + "fmt" "os" "sort" "time" + "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -66,27 +67,33 @@ func runListCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrNetwork) } - if globals.OutputFormat == "json" { - feedback.PrintJSON(ports) - } else { - outputListResp(ports) - } + feedback.PrintResult(result{ports}) +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type result struct { + ports []*rpc.DetectedPort +} + +func (dr result) Data() interface{} { + return dr.ports } -func outputListResp(ports []*rpc.DetectedPort) { - if len(ports) == 0 { - feedback.Print("No boards found.") - return +func (dr result) String() string { + if len(dr.ports) == 0 { + return "No boards found." } - sort.Slice(ports, func(i, j int) bool { - x, y := ports[i], ports[j] + + sort.Slice(dr.ports, func(i, j int) bool { + x, y := dr.ports[i], dr.ports[j] return x.GetProtocol() < y.GetProtocol() || (x.GetProtocol() == y.GetProtocol() && x.GetAddress() < y.GetAddress()) }) t := table.New() - t.SetHeader("Port", "Type", "Board Name", "FQBN") - for _, port := range ports { + t.SetHeader("Port", "Type", "Board Name", "FQBN", "Core") + for _, port := range dr.ports { address := port.GetProtocol() + "://" + port.GetAddress() if port.GetProtocol() == "serial" { address = port.GetAddress() @@ -99,17 +106,27 @@ func outputListResp(ports []*rpc.DetectedPort) { }) for _, b := range boards { board := b.GetName() - fqbn := b.GetFQBN() - t.AddRow(address, protocol, board, fqbn) - // show address and protocol only on the first row + + // to improve the user experience, show on a dedicated column + // the name of the core supporting the board detected + var coreName = "" + fqbn, err := cores.ParseFQBN(b.GetFQBN()) + if err == nil { + coreName = fmt.Sprintf("%s:%s", fqbn.Package, fqbn.PlatformArch) + } + + t.AddRow(address, protocol, board, fqbn, coreName) + + // reset address and protocol, we only show them on the first row address = "" protocol = "" } } else { board := "Unknown" fqbn := "" - t.AddRow(address, protocol, board, fqbn) + coreName := "" + t.AddRow(address, protocol, board, fqbn, coreName) } } - feedback.Print(t.Render()) + return t.Render() } diff --git a/cli/board/listall.go b/cli/board/listall.go index 0034c6c6827..47472a69cc6 100644 --- a/cli/board/listall.go +++ b/cli/board/listall.go @@ -24,7 +24,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/board" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -58,22 +57,28 @@ func runListAllCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if globals.OutputFormat == "json" { - feedback.PrintJSON(list) - } else { - outputBoardListAll(list) - } + feedback.PrintResult(resultAll{list}) +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type resultAll struct { + list *rpc.BoardListAllResp +} + +func (dr resultAll) Data() interface{} { + return dr.list } -func outputBoardListAll(list *rpc.BoardListAllResp) { - sort.Slice(list.Boards, func(i, j int) bool { - return list.Boards[i].GetName() < list.Boards[j].GetName() +func (dr resultAll) String() string { + sort.Slice(dr.list.Boards, func(i, j int) bool { + return dr.list.Boards[i].GetName() < dr.list.Boards[j].GetName() }) t := table.New() t.SetHeader("Board Name", "FQBN") - for _, item := range list.GetBoards() { + for _, item := range dr.list.GetBoards() { t.AddRow(item.GetName(), item.GetFQBN()) } - feedback.Print(t.Render()) + return t.Render() } diff --git a/cli/cli.go b/cli/cli.go index 4fb884f9b6b..12450fa7ea0 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -33,6 +33,7 @@ import ( "github.com/arduino/arduino-cli/cli/generatedocs" "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/lib" + "github.com/arduino/arduino-cli/cli/output" "github.com/arduino/arduino-cli/cli/sketch" "github.com/arduino/arduino-cli/cli/upload" "github.com/arduino/arduino-cli/cli/version" @@ -52,9 +53,10 @@ var ( PersistentPreRun: preRun, } - verbose bool - logFile string - logFormat string + verbose bool + logFile string + logFormat string + outputFormat string ) const ( @@ -80,10 +82,10 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.AddCommand(version.NewCommand()) cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the logs on the standard output.") - cmd.PersistentFlags().StringVar(&globals.LogLevel, "log-level", defaultLogLevel, "Messages with this level and above will be logged (default: warn).") + cmd.PersistentFlags().StringVar(&globals.LogLevel, "log-level", defaultLogLevel, "Messages with this level and above will be logged.") cmd.PersistentFlags().StringVar(&logFile, "log-file", "", "Path to the file where logs will be written.") cmd.PersistentFlags().StringVar(&logFormat, "log-format", "text", "The output format for the logs, can be [text|json].") - cmd.PersistentFlags().StringVar(&globals.OutputFormat, "format", "text", "The output format, can be [text|json].") + cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", "The output format, can be [text|json].") cmd.PersistentFlags().StringVar(&globals.YAMLConfigFile, "config-file", "", "The custom config file (if not specified the default will be used).") cmd.PersistentFlags().StringSliceVar(&globals.AdditionalUrls, "additional-urls", []string{}, "Additional URLs for the board manager.") } @@ -115,7 +117,9 @@ func parseFormatString(arg string) (feedback.OutputFormat, bool) { func preRun(cmd *cobra.Command, args []string) { // normalize the format strings - globals.OutputFormat = strings.ToLower(globals.OutputFormat) + outputFormat = strings.ToLower(outputFormat) + // configure the output package + output.OutputFormat = outputFormat logFormat = strings.ToLower(logFormat) // should we log to file? @@ -159,9 +163,9 @@ func preRun(cmd *cobra.Command, args []string) { } // check the right output format was passed - format, found := parseFormatString(globals.OutputFormat) + format, found := parseFormatString(outputFormat) if !found { - feedback.Error("Invalid output format: " + globals.OutputFormat) + feedback.Error("Invalid output format: " + outputFormat) os.Exit(errorcodes.ErrBadCall) } @@ -174,7 +178,7 @@ func preRun(cmd *cobra.Command, args []string) { logrus.Info("Starting root command preparation (`arduino`)") logrus.Info("Formatter set") - if globals.OutputFormat != "text" { + if outputFormat != "text" { cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { logrus.Warn("Calling help on JSON format") feedback.Error("Invalid Call : should show Help, but it is available only in TEXT mode.") diff --git a/cli/cli_test.go b/cli/cli_test.go index e12c4bb57f4..217712ac4be 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -1,19 +1,20 @@ -/* - * This file is part of arduino-cli. - * - * Copyright 2018 ARDUINO SA (http://www.arduino.cc/) - * - * This software is released under the GNU General Public License version 3, - * which covers the main part of arduino-cli. - * The terms of this license can be found at: - * https://www.gnu.org/licenses/gpl-3.0.en.html - * - * You can be released from the requirements of the above licenses by purchasing - * a commercial license. Buying such a license is mandatory if you want to modify or - * otherwise use the software for commercial activities involving the Arduino - * software without disclosing the source code of your own applications. To purchase - * a commercial license, send an email to license@arduino.cc. - */ +// This file is part of arduino-cli. +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +// These tests are mocked and won't work on OSX +// +build !darwin package cli @@ -296,12 +297,6 @@ func TestUploadIntegration(t *testing.T) { require.NotZero(t, exitCode) } -func TestSketchCommandsIntegration(t *testing.T) { - exitCode, d := executeWithArgs("sketch", "new", "Test") - require.Zero(t, exitCode) - require.Contains(t, string(d), "Sketch created") -} - func TestCompileCommandsIntegration(t *testing.T) { // Set staging dir to a temporary dir tmp := tmpDirOrDie() @@ -319,11 +314,11 @@ func TestCompileCommandsIntegration(t *testing.T) { require.Zero(t, exitCode) // Create a test sketch - exitCode, d := executeWithArgs("sketch", "new", "Test1") + test1 := filepath.Join(currSketchbookDir, "Test1") + exitCode, d := executeWithArgs("sketch", "new", test1) require.Zero(t, exitCode) // Build sketch without FQBN - test1 := filepath.Join(currSketchbookDir, "Test1") exitCode, d = executeWithArgs("compile", test1) require.NotZero(t, exitCode) require.Contains(t, string(d), "no FQBN provided") @@ -402,6 +397,39 @@ board_manager: require.NotZero(t, exitCode) } +func Test3rdPartyCoreIntegration(t *testing.T) { + // override SetUp dirs + tmp := tmpDirOrDie() + defer os.RemoveAll(tmp) + os.Setenv("ARDUINO_SKETCHBOOK_DIR", tmp) + currSketchbookDir = tmp + + configFile := filepath.Join(currDataDir, "arduino-cli.yaml") + err := ioutil.WriteFile(configFile, []byte(` +board_manager: + additional_urls: + - https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json +`), os.FileMode(0644)) + require.NoError(t, err, "writing dummy config "+configFile) + + // Update index and install esp32:esp32 + exitCode, _ := executeWithArgs("--config-file", configFile, "core", "update-index") + require.Zero(t, exitCode) + exitCode, d := executeWithArgs("--config-file", configFile, "core", "install", "esp32:esp32") + require.Zero(t, exitCode) + require.Contains(t, string(d), "installed") + + // Build a simple sketch and check if all artifacts are copied + tmpSketch := paths.New(currSketchbookDir).Join("Blink") + err = paths.New("testdata/Blink").CopyDirTo(tmpSketch) + require.NoError(t, err, "copying test sketch into temp dir") + exitCode, d = executeWithArgs("--config-file", configFile, "compile", "-b", "esp32:esp32:esp32", tmpSketch.String()) + require.Zero(t, exitCode) + require.True(t, tmpSketch.Join("Blink.esp32.esp32.esp32.bin").Exist()) + require.True(t, tmpSketch.Join("Blink.esp32.esp32.esp32.elf").Exist()) + require.True(t, tmpSketch.Join("Blink.esp32.esp32.esp32.partitions.bin").Exist()) // https://github.com/arduino/arduino-cli/issues/163 +} + func TestCoreCommandsIntegration(t *testing.T) { // override SetUp dirs tmp := tmpDirOrDie() diff --git a/cli/config/dump.go b/cli/config/dump.go index a1f56e536e0..8b8787c8192 100644 --- a/cli/config/dump.go +++ b/cli/config/dump.go @@ -58,6 +58,21 @@ var dumpCmd = &cobra.Command{ Run: runDumpCommand, } +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type dumpResult struct { + structured *jsonConfig + plain string +} + +func (dr dumpResult) Data() interface{} { + return dr.structured +} + +func (dr dumpResult) String() string { + return dr.plain +} + func runDumpCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino config dump`") @@ -69,23 +84,23 @@ func runDumpCommand(cmd *cobra.Command, args []string) { c := globals.Config - if globals.OutputFormat == "json" { - sketchbookDir := "" - if c.SketchbookDir != nil { - sketchbookDir = c.SketchbookDir.String() - } + sketchbookDir := "" + if c.SketchbookDir != nil { + sketchbookDir = c.SketchbookDir.String() + } - arduinoDataDir := "" - if c.DataDir != nil { - arduinoDataDir = c.DataDir.String() - } + arduinoDataDir := "" + if c.DataDir != nil { + arduinoDataDir = c.DataDir.String() + } - arduinoDownloadsDir := "" - if c.ArduinoDownloadsDir != nil { - arduinoDownloadsDir = c.ArduinoDownloadsDir.String() - } + arduinoDownloadsDir := "" + if c.ArduinoDownloadsDir != nil { + arduinoDownloadsDir = c.ArduinoDownloadsDir.String() + } - feedback.PrintJSON(jsonConfig{ + feedback.PrintResult(&dumpResult{ + structured: &jsonConfig{ ProxyType: c.ProxyType, ProxyManualConfig: &jsonProxyConfig{ Hostname: c.ProxyHostname, @@ -98,8 +113,7 @@ func runDumpCommand(cmd *cobra.Command, args []string) { BoardsManager: &jsonBoardsManagerConfig{ AdditionalURLS: c.BoardManagerAdditionalUrls, }, - }) - } else { - feedback.Print(string(data)) - } + }, + plain: string(data), + }) } diff --git a/cli/config/init.go b/cli/config/init.go index 66998e02437..7d7efbc9eed 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -40,8 +40,6 @@ func initInitCommand() *cobra.Command { Args: cobra.NoArgs, Run: runInitCommand, } - initCommand.Flags().BoolVar(&initFlags._default, "default", false, - "If omitted, ask questions to the user about setting configuration properties, otherwise use default configuration.") initCommand.Flags().StringVar(&initFlags.location, "save-as", "", "Sets where to save the configuration file [default is ./arduino-cli.yaml].") return initCommand @@ -55,13 +53,6 @@ var initFlags struct { func runInitCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino config init`") - if !initFlags._default { - if globals.OutputFormat != "text" { - feedback.Error("The interactive mode is supported only in text mode.") - os.Exit(errorcodes.ErrBadCall) - } - } - filepath := initFlags.location if filepath == "" { filepath = globals.Config.ConfigFile.String() diff --git a/cli/core/download.go b/cli/core/download.go index 0506ab96148..22d00875689 100644 --- a/cli/core/download.go +++ b/cli/core/download.go @@ -64,7 +64,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { Version: platformRef.Version, } _, err := core.PlatformDownload(context.Background(), platformDownloadreq, output.ProgressBar(), - globals.HTTPClientHeader) + globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error downloading %s: %v", args[i], err) os.Exit(errorcodes.ErrNetwork) diff --git a/cli/core/install.go b/cli/core/install.go index 29e3022eaec..cc0135a0fa3 100644 --- a/cli/core/install.go +++ b/cli/core/install.go @@ -65,7 +65,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { Version: platformRef.Version, } _, err := core.PlatformInstall(context.Background(), plattformInstallReq, output.ProgressBar(), - output.TaskProgress(), globals.HTTPClientHeader) + output.TaskProgress(), globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error during install: %v", err) os.Exit(errorcodes.ErrGeneric) diff --git a/cli/core/list.go b/cli/core/list.go index d45f1835f38..c85fa4b290c 100644 --- a/cli/core/list.go +++ b/cli/core/list.go @@ -24,7 +24,6 @@ import ( "github.com/arduino/arduino-cli/arduino/cores" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" "github.com/arduino/arduino-cli/table" @@ -59,26 +58,32 @@ func runListCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if globals.OutputFormat == "json" { - feedback.PrintJSON(platforms) - } else { - outputInstalledCores(platforms) - } + feedback.PrintResult(installedResult{platforms}) +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type installedResult struct { + platforms []*cores.PlatformRelease +} + +func (ir installedResult) Data() interface{} { + return ir.platforms } -func outputInstalledCores(platforms []*cores.PlatformRelease) { - if platforms == nil || len(platforms) == 0 { - return +func (ir installedResult) String() string { + if ir.platforms == nil || len(ir.platforms) == 0 { + return "" } t := table.New() t.SetHeader("ID", "Installed", "Latest", "Name") - sort.Slice(platforms, func(i, j int) bool { - return platforms[i].Platform.String() < platforms[j].Platform.String() + sort.Slice(ir.platforms, func(i, j int) bool { + return ir.platforms[i].Platform.String() < ir.platforms[j].Platform.String() }) - for _, p := range platforms { + for _, p := range ir.platforms { t.AddRow(p.Platform.String(), p.Version.String(), p.Platform.GetLatestRelease().Version.String(), p.Platform.Name) } - feedback.Print(t.Render()) + return t.Render() } diff --git a/cli/core/search.go b/cli/core/search.go index 4143777886b..4d38e41a14b 100644 --- a/cli/core/search.go +++ b/cli/core/search.go @@ -25,7 +25,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/core" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -40,7 +39,7 @@ func initSearchCommand() *cobra.Command { Short: "Search for a core in the package index.", Long: "Search for a core in the package index using the specified keywords.", Example: " " + os.Args[0] + " core search MKRZero -v", - Args: cobra.MinimumNArgs(1), + Args: cobra.ArbitraryArgs, Run: runSearchCommand, } return searchCommand @@ -51,11 +50,6 @@ func runSearchCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino core search`") arguments := strings.ToLower(strings.Join(args, " ")) - - if globals.OutputFormat != "json" { - feedback.Printf("Searching for platforms matching '%s'", arguments) - } - resp, err := core.PlatformSearch(context.Background(), &rpc.PlatformSearchReq{ Instance: instance, SearchArgs: arguments, @@ -66,25 +60,30 @@ func runSearchCommand(cmd *cobra.Command, args []string) { } coreslist := resp.GetSearchOutput() - if globals.OutputFormat == "json" { - feedback.PrintJSON(coreslist) - } else { - outputSearchCores(coreslist) - } + feedback.PrintResult(searchResults{coreslist}) +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type searchResults struct { + platforms []*rpc.Platform +} + +func (sr searchResults) Data() interface{} { + return sr.platforms } -func outputSearchCores(cores []*rpc.Platform) { - if len(cores) > 0 { +func (sr searchResults) String() string { + if len(sr.platforms) > 0 { t := table.New() t.SetHeader("ID", "Version", "Name") - sort.Slice(cores, func(i, j int) bool { - return cores[i].ID < cores[j].ID + sort.Slice(sr.platforms, func(i, j int) bool { + return sr.platforms[i].ID < sr.platforms[j].ID }) - for _, item := range cores { + for _, item := range sr.platforms { t.AddRow(item.GetID(), item.GetLatest(), item.GetName()) } - feedback.Print(t.Render()) - } else { - feedback.Print("No platforms matching your search.") + return t.Render() } + return "No platforms matching your search." } diff --git a/cli/core/upgrade.go b/cli/core/upgrade.go index 838d16d67cd..5bee93f1441 100644 --- a/cli/core/upgrade.go +++ b/cli/core/upgrade.go @@ -90,7 +90,7 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { Architecture: platformRef.Architecture, } - _, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) + _, err := core.PlatformUpgrade(context.Background(), r, output.ProgressBar(), output.TaskProgress(), globals.NewHTTPClientHeader()) if err == core.ErrAlreadyLatest { feedback.Printf("Platform %s is already at the latest version", platformRef) } else if err != nil { diff --git a/cli/daemon/daemon.go b/cli/daemon/daemon.go index 211fc887b72..bfbdeea0f92 100644 --- a/cli/daemon/daemon.go +++ b/cli/daemon/daemon.go @@ -41,7 +41,7 @@ const ( func NewCommand() *cobra.Command { return &cobra.Command{ Use: "daemon", - Short: "Run as a daemon", + Short: fmt.Sprintf("Run as a daemon on port %s", port), Long: "Running as a daemon the initialization of cores and libraries is done only once.", Example: " " + os.Args[0] + " daemon", Args: cobra.NoArgs, diff --git a/cli/feedback/exported.go b/cli/feedback/exported.go index a3cc2a6f23d..aee07535dbd 100644 --- a/cli/feedback/exported.go +++ b/cli/feedback/exported.go @@ -52,8 +52,8 @@ func Printf(format string, v ...interface{}) { } // Print behaves like fmt.Print but writes on the out writer and adds a newline. -func Print(v ...interface{}) { - fb.Print(v...) +func Print(v interface{}) { + fb.Print(v) } // Errorf behaves like fmt.Printf but writes on the error writer and adds a @@ -68,13 +68,9 @@ func Error(v ...interface{}) { fb.Error(v...) } -// PrintJSON is a convenient wrapper to provide feedback by printing the -// desired output in a pretty JSON format. It adds a newline to the output. -func PrintJSON(v interface{}) { - fb.PrintJSON(v) -} - -// PrintResult is a convenient wrapper... +// PrintResult is a convenient wrapper to provide feedback for complex data, +// where the contents can't be just serialized to JSON but requires more +// structure. func PrintResult(res Result) { fb.PrintResult(res) } diff --git a/cli/feedback/feedback.go b/cli/feedback/feedback.go index 1f355c9f869..9ccc5f4671d 100644 --- a/cli/feedback/feedback.go +++ b/cli/feedback/feedback.go @@ -86,8 +86,12 @@ func (fb *Feedback) Printf(format string, v ...interface{}) { } // Print behaves like fmt.Print but writes on the out writer and adds a newline. -func (fb *Feedback) Print(v ...interface{}) { - fmt.Fprintln(fb.out, v...) +func (fb *Feedback) Print(v interface{}) { + if fb.format == JSON { + fb.printJSON(v) + } else { + fmt.Fprintln(fb.out, v) + } } // Errorf behaves like fmt.Printf but writes on the error writer and adds a @@ -103,24 +107,22 @@ func (fb *Feedback) Error(v ...interface{}) { logrus.Error(fmt.Sprint(v...)) } -// PrintJSON is a convenient wrapper to provide feedback by printing the +// printJSON is a convenient wrapper to provide feedback by printing the // desired output in a pretty JSON format. It adds a newline to the output. -func (fb *Feedback) PrintJSON(v interface{}) { +func (fb *Feedback) printJSON(v interface{}) { if d, err := json.MarshalIndent(v, "", " "); err != nil { fb.Errorf("Error during JSON encoding of the output: %v", err) } else { - fb.Print(string(d)) + fmt.Fprint(fb.out, string(d)) } } -// PrintResult is a convenient wrapper... +// PrintResult is a convenient wrapper to provide feedback for complex data, +// where the contents can't be just serialized to JSON but requires more +// structure. func (fb *Feedback) PrintResult(res Result) { if fb.format == JSON { - if d, err := json.MarshalIndent(res.Data(), "", " "); err != nil { - fb.Errorf("Error during JSON encoding of the output: %v", err) - } else { - fb.Print(string(d)) - } + fb.printJSON(res.Data()) } else { fb.Print(fmt.Sprintf("%s", res)) } diff --git a/cli/globals/configs.go b/cli/globals/configs.go index c17ff616cda..d0c961cb45a 100644 --- a/cli/globals/configs.go +++ b/cli/globals/configs.go @@ -66,11 +66,11 @@ func InitConfigs() { // Read configuration from old configuration file if found, but output a warning. if old := paths.New(".cli-config.yml"); old.Exist() { logrus.Errorf("Old configuration file detected: %s.", old) - logrus.Info("The name of this file has been changed to `arduino-yaml`, please rename the file fix it.") + logrus.Info("The name of this file has been changed to `arduino-cli.yaml`, please rename the file fix it.") feedback.Error( fmt.Errorf("WARNING: Old configuration file detected: %s", old), "The name of this file has been changed to `arduino-yaml`, in a future release we will not support"+ - "the old name `.cli-config.yml` anymore. Please rename the file to `arduino-yaml` to silence this warning.") + "the old name `.cli-config.yml` anymore. Please rename the file to `arduino-cli.yaml` to silence this warning.") readConfigFrom(old) } diff --git a/cli/globals/globals.go b/cli/globals/globals.go index 12863bc0b83..fd46e0a6f09 100644 --- a/cli/globals/globals.go +++ b/cli/globals/globals.go @@ -29,10 +29,6 @@ import ( var ( // Debug determines whether to dump debug output to stderr or not Debug bool - // OutputFormat can be "text" or "json" - OutputFormat string - // HTTPClientHeader is the object that will be propagated to configure the clients inside the downloaders - HTTPClientHeader = getHTTPClientHeader() // VersionInfo contains all info injected during build VersionInfo = version.NewInfo(filepath.Base(os.Args[0])) // Config FIXMEDOC @@ -46,9 +42,9 @@ var ( LogLevel string ) -func getHTTPClientHeader() http.Header { +// NewHTTPClientHeader returns the http.Header object that must be used by the clients inside the downloaders +func NewHTTPClientHeader() http.Header { userAgentValue := fmt.Sprintf("%s/%s (%s; %s; %s) Commit:%s", VersionInfo.Application, VersionInfo.VersionString, runtime.GOARCH, runtime.GOOS, runtime.Version(), VersionInfo.Commit) - downloaderHeaders := http.Header{"User-Agent": []string{userAgentValue}} - return downloaderHeaders + return http.Header{"User-Agent": []string{userAgentValue}} } diff --git a/cli/instance/instance.go b/cli/instance/instance.go index d4aec847902..037130624ed 100644 --- a/cli/instance/instance.go +++ b/cli/instance/instance.go @@ -36,7 +36,7 @@ func initInstance() *rpc.InitResp { logrus.Info("Initializing package manager") req := packageManagerInitReq() - resp, err := commands.Init(context.Background(), req, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) + resp, err := commands.Init(context.Background(), req, output.ProgressBar(), output.TaskProgress(), globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error initializing package manager: %v", err) os.Exit(errorcodes.ErrGeneric) diff --git a/cli/lib/download.go b/cli/lib/download.go index f458b117471..11b10602e07 100644 --- a/cli/lib/download.go +++ b/cli/lib/download.go @@ -60,7 +60,7 @@ func runDownloadCommand(cmd *cobra.Command, args []string) { Version: library.Version, } _, err := lib.LibraryDownload(context.Background(), libraryDownloadReq, output.ProgressBar(), - globals.HTTPClientHeader) + globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error downloading %s: %v", library, err) os.Exit(errorcodes.ErrNetwork) diff --git a/cli/lib/install.go b/cli/lib/install.go index cfcae9fe76a..94be4e706dc 100644 --- a/cli/lib/install.go +++ b/cli/lib/install.go @@ -60,7 +60,7 @@ func runInstallCommand(cmd *cobra.Command, args []string) { Version: library.Version, } err := lib.LibraryInstall(context.Background(), libraryInstallReq, output.ProgressBar(), - output.TaskProgress(), globals.HTTPClientHeader) + output.TaskProgress(), globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error installing %s: %v", library, err) os.Exit(errorcodes.ErrGeneric) diff --git a/cli/lib/list.go b/cli/lib/list.go index 0de750b4529..495174caf0f 100644 --- a/cli/lib/list.go +++ b/cli/lib/list.go @@ -22,7 +22,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -66,27 +65,31 @@ func runListCommand(cmd *cobra.Command, args []string) { } libs := res.GetInstalledLibrary() - if libs != nil { - if globals.OutputFormat == "json" { - feedback.PrintJSON(libs) - } else { - outputListLibrary(libs) - } - } + feedback.PrintResult(installedResult{libs}) logrus.Info("Done") } -func outputListLibrary(il []*rpc.InstalledLibrary) { - if il == nil || len(il) == 0 { - return +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type installedResult struct { + installedLibs []*rpc.InstalledLibrary +} + +func (ir installedResult) Data() interface{} { + return ir.installedLibs +} + +func (ir installedResult) String() string { + if ir.installedLibs == nil || len(ir.installedLibs) == 0 { + return "No libraries installed." } t := table.New() t.SetHeader("Name", "Installed", "Available", "Location") lastName := "" - for _, libMeta := range il { + for _, libMeta := range ir.installedLibs { lib := libMeta.GetLibrary() name := lib.Name if name == lastName { @@ -110,5 +113,5 @@ func outputListLibrary(il []*rpc.InstalledLibrary) { } } - feedback.Print(t.Render()) + return t.Render() } diff --git a/cli/lib/search.go b/cli/lib/search.go index 4014acc56fc..5c47df9019d 100644 --- a/cli/lib/search.go +++ b/cli/lib/search.go @@ -26,7 +26,6 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/cli/instance" "github.com/arduino/arduino-cli/commands/lib" rpc "github.com/arduino/arduino-cli/rpc/commands" @@ -64,63 +63,76 @@ func runSearchCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - if globals.OutputFormat == "json" { - if searchFlags.namesOnly { - type LibName struct { - Name string `json:"name,required"` - } - - type NamesOnly struct { - Libraries []LibName `json:"libraries,required"` - } - - names := []LibName{} - results := searchResp.GetLibraries() - for _, lsr := range results { - names = append(names, LibName{lsr.Name}) - } - feedback.PrintJSON(NamesOnly{ - names, - }) - } else { - feedback.PrintJSON(searchResp) + feedback.PrintResult(result{ + results: searchResp, + namesOnly: searchFlags.namesOnly, + }) + + logrus.Info("Done") +} + +// output from this command requires special formatting, let's create a dedicated +// feedback.Result implementation +type result struct { + results *rpc.LibrarySearchResp + namesOnly bool +} + +func (res result) Data() interface{} { + if res.namesOnly { + type LibName struct { + Name string `json:"name,required"` + } + + type NamesOnly struct { + Libraries []LibName `json:"libraries,required"` + } + + names := []LibName{} + results := res.results.GetLibraries() + for _, lsr := range results { + names = append(names, LibName{lsr.Name}) + } + + return NamesOnly{ + names, } - } else { - // get a sorted slice of results - results := searchResp.GetLibraries() - sort.Slice(results, func(i, j int) bool { - return results[i].Name < results[j].Name - }) - - // print all the things - outputSearchedLibrary(results, searchFlags.namesOnly) } - logrus.Info("Done") + return res.results } -func outputSearchedLibrary(results []*rpc.SearchedLibrary, namesOnly bool) { +func (res result) String() string { + results := res.results.GetLibraries() if len(results) == 0 { - feedback.Print("No libraries matching your search.") - return + return "No libraries matching your search." } + // get a sorted slice of results + sort.Slice(results, func(i, j int) bool { + return results[i].Name < results[j].Name + }) + + var out strings.Builder + for _, lsr := range results { - feedback.Printf(`Name: "%s"`, lsr.Name) - if namesOnly { + out.WriteString(fmt.Sprintf("Name: \"%s\"\n", lsr.Name)) + if res.namesOnly { continue } - feedback.Printf(" Author: %s", lsr.GetLatest().Author) - feedback.Printf(" Maintainer: %s", lsr.GetLatest().Maintainer) - feedback.Printf(" Sentence: %s", lsr.GetLatest().Sentence) - feedback.Printf(" Paragraph: %s", lsr.GetLatest().Paragraph) - feedback.Printf(" Website: %s", lsr.GetLatest().Website) - feedback.Printf(" Category: %s", lsr.GetLatest().Category) - feedback.Printf(" Architecture: %s", strings.Join(lsr.GetLatest().Architectures, ", ")) - feedback.Printf(" Types: %s", strings.Join(lsr.GetLatest().Types, ", ")) - feedback.Printf(" Versions: %s", strings.Replace(fmt.Sprint(versionsFromSearchedLibrary(lsr)), " ", ", ", -1)) + out.WriteString(fmt.Sprintf(" Author: %s\n", lsr.GetLatest().Author)) + out.WriteString(fmt.Sprintf(" Maintainer: %s\n", lsr.GetLatest().Maintainer)) + out.WriteString(fmt.Sprintf(" Sentence: %s\n", lsr.GetLatest().Sentence)) + out.WriteString(fmt.Sprintf(" Paragraph: %s\n", lsr.GetLatest().Paragraph)) + out.WriteString(fmt.Sprintf(" Website: %s\n", lsr.GetLatest().Website)) + out.WriteString(fmt.Sprintf(" Category: %s\n", lsr.GetLatest().Category)) + out.WriteString(fmt.Sprintf(" Architecture: %s\n", strings.Join(lsr.GetLatest().Architectures, ", "))) + out.WriteString(fmt.Sprintf(" Types: %s\n", strings.Join(lsr.GetLatest().Types, ", "))) + out.WriteString(fmt.Sprintf(" Versions: %s\n", strings.Replace(fmt.Sprint(versionsFromSearchedLibrary(lsr)), " ", ", ", -1))) } + + return fmt.Sprintf("%s", out.String()) } func versionsFromSearchedLibrary(library *rpc.SearchedLibrary) []*semver.Version { diff --git a/cli/lib/upgrade.go b/cli/lib/upgrade.go index fe4a90fc510..06ef2a73abe 100644 --- a/cli/lib/upgrade.go +++ b/cli/lib/upgrade.go @@ -50,13 +50,13 @@ func runUpgradeCommand(cmd *cobra.Command, args []string) { instance := instance.CreateInstaceIgnorePlatformIndexErrors() if len(args) == 0 { - err := lib.LibraryUpgradeAll(instance.Id, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) + err := lib.LibraryUpgradeAll(instance.Id, output.ProgressBar(), output.TaskProgress(), globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error upgrading libraries: %v", err) os.Exit(errorcodes.ErrGeneric) } } else { - err := lib.LibraryUpgrade(instance.Id, args, output.ProgressBar(), output.TaskProgress(), globals.HTTPClientHeader) + err := lib.LibraryUpgrade(instance.Id, args, output.ProgressBar(), output.TaskProgress(), globals.NewHTTPClientHeader()) if err != nil { feedback.Errorf("Error upgrading libraries: %v", err) os.Exit(errorcodes.ErrGeneric) diff --git a/cli/output/rpc_progress.go b/cli/output/rpc_progress.go index e6b61a2e1ea..ec47de305a3 100644 --- a/cli/output/rpc_progress.go +++ b/cli/output/rpc_progress.go @@ -20,16 +20,18 @@ package output import ( "fmt" - "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/commands" rpc "github.com/arduino/arduino-cli/rpc/commands" "github.com/cmaglie/pb" ) +// OutputFormat can be "text" or "json" +var OutputFormat string + // ProgressBar returns a DownloadProgressCB that prints a progress bar. // If JSON output format has been selected, the callback outputs nothing. func ProgressBar() commands.DownloadProgressCB { - if globals.OutputFormat != "json" { + if OutputFormat != "json" { return NewDownloadProgressBarCB() } return func(curr *rpc.DownloadProgress) { @@ -40,7 +42,7 @@ func ProgressBar() commands.DownloadProgressCB { // TaskProgress returns a TaskProgressCB that prints the task progress. // If JSON output format has been selected, the callback outputs nothing. func TaskProgress() commands.TaskProgressCB { - if globals.OutputFormat != "json" { + if OutputFormat != "json" { return NewTaskProgressCB() } return func(curr *rpc.TaskProgress) { diff --git a/cli/sketch/new.go b/cli/sketch/new.go index 8121a152194..6b2e06a122f 100644 --- a/cli/sketch/new.go +++ b/cli/sketch/new.go @@ -18,11 +18,13 @@ package sketch import ( + "io/ioutil" "os" + "path/filepath" + "strings" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cli/cli/globals" "github.com/spf13/cobra" ) @@ -47,17 +49,23 @@ void loop() { `) func runNewCommand(cmd *cobra.Command, args []string) { - sketchDir := globals.Config.SketchbookDir.Join(args[0]) - if err := sketchDir.MkdirAll(); err != nil { + // Trim to avoid issues if user creates a sketch adding the .ino extesion to the name + trimmedSketchName := strings.TrimSuffix(args[0], ".ino") + sketchDir, err := filepath.Abs(trimmedSketchName) + if err != nil { + feedback.Errorf("Error creating sketch: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + if err := os.MkdirAll(sketchDir, os.FileMode(0755)); err != nil { feedback.Errorf("Could not create sketch directory: %v", err) os.Exit(errorcodes.ErrGeneric) } - - sketchFile := sketchDir.Join(args[0] + ".ino") - if err := sketchFile.WriteFile(emptySketch); err != nil { + sketchName := filepath.Base(sketchDir) + sketchFile := filepath.Join(sketchDir, sketchName+".ino") + if err := ioutil.WriteFile(sketchFile, emptySketch, os.FileMode(0644)); err != nil { feedback.Errorf("Error creating sketch: %v", err) os.Exit(errorcodes.ErrGeneric) } - feedback.Print("Sketch created in: " + sketchDir.String()) + feedback.Print("Sketch created in: " + sketchDir) } diff --git a/cli/testdata/Blink/Blink.ino b/cli/testdata/Blink/Blink.ino new file mode 100644 index 00000000000..5054c040393 --- /dev/null +++ b/cli/testdata/Blink/Blink.ino @@ -0,0 +1,3 @@ + +void setup() {} +void loop() {} diff --git a/cli/version/version.go b/cli/version/version.go index 15c90054b2c..6c083c20ed1 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -38,9 +38,5 @@ func NewCommand() *cobra.Command { } func run(cmd *cobra.Command, args []string) { - if globals.OutputFormat == "json" { - feedback.PrintJSON(globals.VersionInfo) - } else { - feedback.Print(globals.VersionInfo) - } + feedback.Print(globals.VersionInfo) } diff --git a/client_example/go.mod b/client_example/go.mod index da515291670..4d3f9b5512f 100644 --- a/client_example/go.mod +++ b/client_example/go.mod @@ -1,6 +1,6 @@ module github.com/arduino/arduino-cli/client_example -go 1.12 +go 1.13 require ( github.com/arduino/arduino-cli v0.0.0-20190826141027-35722fda467d diff --git a/client_example/main.go b/client_example/main.go index 5008efd96fd..9c1fd91b3a4 100644 --- a/client_example/main.go +++ b/client_example/main.go @@ -167,7 +167,9 @@ func initInstance(client rpc.ArduinoCoreClient) *rpc.Instance { // the data folder. initRespStream, err := client.Init(context.Background(), &rpc.InitReq{ Configuration: &rpc.Configuration{ - DataDir: dataDir, + DataDir: dataDir, + SketchbookDir: filepath.Join(dataDir, "sketchbook"), + DownloadsDir: filepath.Join(dataDir, "staging"), }, }) if err != nil { @@ -368,11 +370,12 @@ func callBoardsDetails(client rpc.ArduinoCoreClient, instance *rpc.Instance) { } func callBoardAttach(client rpc.ArduinoCoreClient, instance *rpc.Instance) { + currDir, _ := os.Getwd() boardattachresp, err := client.BoardAttach(context.Background(), &rpc.BoardAttachReq{ Instance: instance, BoardUri: "/dev/ttyACM0", - SketchPath: filepath.Join(dataDir, "hello.ino"), + SketchPath: filepath.Join(currDir, "hello.ino"), }) if err != nil { @@ -402,11 +405,12 @@ func callBoardAttach(client rpc.ArduinoCoreClient, instance *rpc.Instance) { } func callCompile(client rpc.ArduinoCoreClient, instance *rpc.Instance) { + currDir, _ := os.Getwd() compRespStream, err := client.Compile(context.Background(), &rpc.CompileReq{ Instance: instance, Fqbn: "arduino:samd:mkr1000", - SketchPath: "hello.ino", + SketchPath: filepath.Join(currDir, "hello.ino"), Verbose: true, }) @@ -440,11 +444,12 @@ func callCompile(client rpc.ArduinoCoreClient, instance *rpc.Instance) { } func callUpload(client rpc.ArduinoCoreClient, instance *rpc.Instance) { + currDir, _ := os.Getwd() uplRespStream, err := client.Upload(context.Background(), &rpc.UploadReq{ Instance: instance, Fqbn: "arduino:samd:mkr1000", - SketchPath: "hello.ino", + SketchPath: filepath.Join(currDir, "hello.ino"), Port: "/dev/ttyACM0", Verbose: true, }) diff --git a/commands/board/list.go b/commands/board/list.go index 5f68b25c1a5..c89f06fe75d 100644 --- a/commands/board/list.go +++ b/commands/board/list.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "net/http" + "regexp" "sync" "github.com/arduino/arduino-cli/cli/globals" @@ -35,12 +36,23 @@ var ( // ErrNotFound is returned when the API returns 404 ErrNotFound = errors.New("board not found") m sync.Mutex + vidPidURL = "https://builder.arduino.cc/v3/boards/byVidPid" + validVidPid = regexp.MustCompile(`0[xX][a-fA-F\d]{4}`) ) -func apiByVidPid(url string) ([]*rpc.BoardListItem, error) { +func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) { + // ensure vid and pid are valid before hitting the API + if !validVidPid.MatchString(vid) { + return nil, errors.Errorf("Invalid vid value: '%s'", vid) + } + if !validVidPid.MatchString(pid) { + return nil, errors.Errorf("Invalid pid value: '%s'", pid) + } + + url := fmt.Sprintf("%s/%s/%s", vidPidURL, vid, pid) retVal := []*rpc.BoardListItem{} req, _ := http.NewRequest("GET", url, nil) - req.Header = globals.HTTPClientHeader + req.Header = globals.NewHTTPClientHeader() req.Header.Set("Content-Type", "application/json") if res, err := http.DefaultClient.Do(req); err == nil { @@ -78,6 +90,17 @@ func apiByVidPid(url string) ([]*rpc.BoardListItem, error) { return retVal, nil } +func identifyViaCloudAPI(port *commands.BoardPort) ([]*rpc.BoardListItem, error) { + // If the port is not USB do not try identification via cloud + id := port.IdentificationPrefs + if !id.ContainsKey("vid") || !id.ContainsKey("pid") { + return nil, ErrNotFound + } + + logrus.Debug("Querying builder API for board identification...") + return apiByVidPid(id.Get("vid"), id.Get("pid")) +} + // List FIXMEDOC func List(instanceID int32) ([]*rpc.DetectedPort, error) { m.Lock() @@ -107,22 +130,20 @@ func List(instanceID int32) ([]*rpc.DetectedPort, error) { } // if installed cores didn't recognize the board, try querying - // the builder API + // the builder API if the board is a USB device port if len(b) == 0 { - logrus.Debug("Querying builder API for board identification...") - url := fmt.Sprintf("https://builder.arduino.cc/v3/boards/byVidPid/%s/%s", - port.IdentificationPrefs.Get("vid"), - port.IdentificationPrefs.Get("pid")) - items, err := apiByVidPid(url) + items, err := identifyViaCloudAPI(port) if err == ErrNotFound { - // the board couldn't be detected, keep going with the next port + // the board couldn't be detected, print a warning logrus.Debug("Board not recognized") - continue } else if err != nil { // this is bad, bail out return nil, errors.Wrap(err, "error getting board info from Arduino Cloud") } + // add a DetectedPort entry in any case: the `Boards` field will + // be empty but the port will be shown anyways (useful for 3rd party + // boards) b = items } diff --git a/commands/board/list_test.go b/commands/board/list_test.go index 65fcaaa8cfb..d8600681c22 100644 --- a/commands/board/list_test.go +++ b/commands/board/list_test.go @@ -21,6 +21,8 @@ import ( "net/http/httptest" "testing" + "github.com/arduino/arduino-cli/commands" + "github.com/arduino/go-properties-orderedmap" "github.com/stretchr/testify/require" ) @@ -40,14 +42,15 @@ func TestGetByVidPid(t *testing.T) { })) defer ts.Close() - res, err := apiByVidPid(ts.URL) + vidPidURL = ts.URL + res, err := apiByVidPid("0xf420", "0XF069") require.Nil(t, err) require.Len(t, res, 1) require.Equal(t, "Arduino/Genuino MKR1000", res[0].Name) require.Equal(t, "arduino:samd:mkr1000", res[0].FQBN) - // wrong url - res, err = apiByVidPid("http://0.0.0.0") + // wrong vid (too long), wrong pid (not an hex value) + res, err = apiByVidPid("0xfffff", "0xDEFG") require.NotNil(t, err) } @@ -57,7 +60,8 @@ func TestGetByVidPidNotFound(t *testing.T) { })) defer ts.Close() - res, err := apiByVidPid(ts.URL) + vidPidURL = ts.URL + res, err := apiByVidPid("0x0420", "0x0069") require.NotNil(t, err) require.Equal(t, "board not found", err.Error()) require.Len(t, res, 0) @@ -70,7 +74,8 @@ func TestGetByVidPid5xx(t *testing.T) { })) defer ts.Close() - res, err := apiByVidPid(ts.URL) + vidPidURL = ts.URL + res, err := apiByVidPid("0x0420", "0x0069") require.NotNil(t, err) require.Equal(t, "the server responded with status 500 Internal Server Error", err.Error()) require.Len(t, res, 0) @@ -82,8 +87,18 @@ func TestGetByVidPidMalformedResponse(t *testing.T) { })) defer ts.Close() - res, err := apiByVidPid(ts.URL) + vidPidURL = ts.URL + res, err := apiByVidPid("0x0420", "0x0069") require.NotNil(t, err) require.Equal(t, "wrong format in server response", err.Error()) require.Len(t, res, 0) } + +func TestBoardDetectionViaAPIWithNonUSBPort(t *testing.T) { + port := &commands.BoardPort{ + IdentificationPrefs: properties.NewMap(), + } + items, err := identifyViaCloudAPI(port) + require.Equal(t, err, ErrNotFound) + require.Empty(t, items) +} diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 9b0a1f380f9..05c9d273cd8 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -95,7 +95,7 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W } if toolsDir, err := config.BundleToolsDirectories(); err == nil { - builderCtx.ToolsDirs = toolsDir + builderCtx.BuiltInToolsDirs = toolsDir } else { return nil, fmt.Errorf("cannot get bundled tools directories: %s", err) } @@ -174,8 +174,11 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W } // FIXME: Make a function to obtain these info... - outputPath := builderCtx.BuildProperties.ExpandPropsInString("{build.path}/{recipe.output.tmp_file}") - ext := filepath.Ext(outputPath) + outputPath := paths.New( + builderCtx.BuildProperties.ExpandPropsInString("{build.path}/{recipe.output.tmp_file}")) // "/build/path/sketch.ino.bin" + ext := outputPath.Ext() // ".hex" | ".bin" + base := outputPath.Base() // "sketch.ino.hex" + base = base[:len(base)-len(ext)] // "sketch.ino" // FIXME: Make a function to produce a better name... // Make the filename without the FQBN configs part @@ -190,7 +193,7 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W } else { exportPath = sketch.FullPath.Parent() } - exportFile = sketch.Name + "." + fqbnSuffix + exportFile = sketch.Name + "." + fqbnSuffix // "sketch.arduino.avr.uno" } else { exportPath = paths.New(req.GetExportFile()).Parent() exportFile = paths.New(req.GetExportFile()).Base() @@ -199,20 +202,31 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W } } - // Copy .hex file to sketch directory - srcHex := paths.New(outputPath) - dstHex := exportPath.Join(exportFile + ext) - logrus.WithField("from", srcHex).WithField("to", dstHex).Debug("copying sketch build output") - if err = srcHex.CopyTo(dstHex); err != nil { - return nil, fmt.Errorf("copying output file: %s", err) + // Copy "sketch.ino.*.hex" / "sketch.ino.*.bin" artifacts to sketch directory + srcDir, err := outputPath.Parent().ReadDir() // read "/build/path/*" + if err != nil { + return nil, fmt.Errorf("reading build directory: %s", err) + } + srcDir.FilterPrefix(base + ".") + srcDir.FilterSuffix(ext) + for _, srcOutput := range srcDir { + srcFilename := srcOutput.Base() // "sketch.ino.*.bin" + srcFilename = srcFilename[len(base):] // ".*.bin" + dstOutput := exportPath.Join(exportFile + srcFilename) + logrus.WithField("from", srcOutput).WithField("to", dstOutput).Debug("copying sketch build output") + if err = srcOutput.CopyTo(dstOutput); err != nil { + return nil, fmt.Errorf("copying output file: %s", err) + } } // Copy .elf file to sketch directory - srcElf := paths.New(outputPath[:len(outputPath)-3] + "elf") - dstElf := exportPath.Join(exportFile + ".elf") - logrus.WithField("from", srcElf).WithField("to", dstElf).Debug("copying sketch build output") - if err = srcElf.CopyTo(dstElf); err != nil { - return nil, fmt.Errorf("copying elf file: %s", err) + srcElf := outputPath.Parent().Join(base + ".elf") + if srcElf.Exist() { + dstElf := exportPath.Join(exportFile + ".elf") + logrus.WithField("from", srcElf).WithField("to", dstElf).Debug("copying sketch build output") + if err = srcElf.CopyTo(dstElf); err != nil { + return nil, fmt.Errorf("copying elf file: %s", err) + } } logrus.Tracef("Compile %s for %s successful", sketch.Name, fqbnIn) diff --git a/commands/daemon/monitor_test.go b/commands/daemon/monitor_test.go index b369e82a48b..e2fd28adb2b 100644 --- a/commands/daemon/monitor_test.go +++ b/commands/daemon/monitor_test.go @@ -1,3 +1,21 @@ +// This file is part of arduino-cli. +// +// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. + +// These tests are mocked and won't work on OSX +// +build !darwin + package daemon_test import ( @@ -99,7 +117,7 @@ func mockOpenSerialMonitor(portName string, baudRate int) (*monitors.SerialMonit return mon, nil } -func TestFoo(t *testing.T) { +func TestConnect(t *testing.T) { monkey.Patch(monitors.OpenSerialMonitor, mockOpenSerialMonitor) svc := daemon.MonitorService{} diff --git a/commands/lib/install.go b/commands/lib/install.go index 0919a6ffb7e..720b95de85d 100644 --- a/commands/lib/install.go +++ b/commands/lib/install.go @@ -59,7 +59,7 @@ func installLibrary(lm *librariesmanager.LibrariesManager, libRelease *libraries logrus.WithField("library", libRelease).Info("Installing library") libPath, libReplaced, err := lm.InstallPrerequisiteCheck(libRelease) if err == librariesmanager.ErrAlreadyInstalled { - taskCB(&rpc.TaskProgress{Message: "Alredy installed " + libRelease.String(), Completed: true}) + taskCB(&rpc.TaskProgress{Message: "Already installed " + libRelease.String(), Completed: true}) return nil } diff --git a/go.mod b/go.mod index 7629cef0d79..b0ae9aa7f2e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/arduino/arduino-cli -go 1.12 +go 1.13 require ( bou.ke/monkey v1.0.1 github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c - github.com/arduino/go-paths-helper v0.0.0-20190214132331-c3c98d1bf2e1 + github.com/arduino/go-paths-helper v1.0.1 github.com/arduino/go-properties-orderedmap v0.0.0-20190828172252-05018b28ff6c github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b @@ -19,7 +19,6 @@ require ( github.com/fsnotify/fsnotify v1.4.7 github.com/go-errors/errors v1.0.1 github.com/golang/protobuf v1.3.2 - github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282 github.com/h2non/filetype v1.0.8 // indirect github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect @@ -42,10 +41,8 @@ require ( go.bug.st/downloader v1.1.0 go.bug.st/relaxed-semver v0.0.0-20181022103824-0265409c5852 go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20190311183353-d8887717615a golang.org/x/text v0.3.0 - google.golang.org/appengine v1.4.0 // indirect google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d // indirect google.golang.org/grpc v1.21.1 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect diff --git a/go.sum b/go.sum index 240d42c3b0f..e54062863df 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c h1:agh2JT96G8egU7FEb13L4dq3fnCN7lxXhJ86t69+W7s= github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8= -github.com/arduino/go-paths-helper v0.0.0-20190214132331-c3c98d1bf2e1 h1:S0NpDSqjlkNA510vmRCP5Cq9mPgu3rWDSdeN4SI1Mwc= -github.com/arduino/go-paths-helper v0.0.0-20190214132331-c3c98d1bf2e1/go.mod h1:OGL+FS3aTrS01YsBAJJhkGuxtEGsFRSgZYo8b3vefdc= -github.com/arduino/go-properties-orderedmap v0.0.0-20181003091528-89278049acd3 h1:aWZoiBr2fCXtZzY4e/TOyQHEFyFpsF9eph7rEDZRv0Y= -github.com/arduino/go-properties-orderedmap v0.0.0-20181003091528-89278049acd3/go.mod h1:kiSuHm7yz3chiy8rb2MphC7ECn3MlkQFAIe4SXmQg6o= +github.com/arduino/go-paths-helper v1.0.1 h1:utYXLM2RfFlc9qp/MJTIYp3t6ux/xM6mWjeEb/WLK4Q= +github.com/arduino/go-paths-helper v1.0.1/go.mod h1:HpxtKph+g238EJHq4geEPv9p+gl3v5YYu35Yb+w31Ck= github.com/arduino/go-properties-orderedmap v0.0.0-20190828172252-05018b28ff6c h1:4z4PJqNH8WGXtm9ix2muUOAP7gxTGBOdQTuKEDyCnsA= github.com/arduino/go-properties-orderedmap v0.0.0-20190828172252-05018b28ff6c/go.mod h1:kiSuHm7yz3chiy8rb2MphC7ECn3MlkQFAIe4SXmQg6o= github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b h1:9hDi4F2st6dbLC3y4i02zFT5quS4X6iioWifGlVwfy4= @@ -15,8 +13,6 @@ github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwG github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/cheynewallace/tabby v1.1.0 h1:XtG/ZanoIvNZHfe0cClhWLzD/16GGF9UD7mMdWwYnCQ= -github.com/cheynewallace/tabby v1.1.0/go.mod h1:Pba/6cUL8uYqvOc9RkyvFbHGrQ9wShyrn6/S/1OYVys= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cmaglie/pb v1.0.27 h1:ynGj8vBXR+dtj4B7Q/W/qGt31771Ux5iFfRQBnwdQiA= github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI= @@ -44,24 +40,29 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282 h1:KFqmdzEPbU7Uck2tn50t+HQXZNVkxe8M9qRb/ZoSHaE= -github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 h1:rhqTjzJlm7EbkELJDKMTU7udov+Se0xZkWmugr6zGok= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc h1:5xUWujf6ES9tEpFHFzI34vcHm8U07lGjxAuJML3qwqM= github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= @@ -94,7 +95,9 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -122,48 +125,33 @@ go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkX golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= -golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190710184609-286818132824 h1:dOGf5KG5e5tnConXcTAnHv2YgmYJtrYjN9b1cMC21TY= -golang.org/x/tools v0.0.0-20190710184609-286818132824/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -172,7 +160,9 @@ google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d/go.mod h1:VzzqZJRn google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/install.sh b/install.sh index 0e6460cea14..cc5dcd0cb6a 100755 --- a/install.sh +++ b/install.sh @@ -78,7 +78,7 @@ checkLatestVersion() { elif [ "$DOWNLOAD_TOOL" = "wget" ]; then tag=$(wget -q -O - $latest_url | grep -o "Release $regex ยท arduino/arduino-cli" | grep -o "$regex") fi - if [ "x$tag" == "x" ]; then + if [ "x$tag" = "x" ]; then echo "Cannot determine latest tag." exit 1 fi @@ -122,7 +122,7 @@ downloadFile() { checkLatestVersion TAG echo "TAG=$TAG" # arduino-cli_0.4.0-rc1_Linux_64bit.[tar.gz, zip] - if [ "$OS" == "Windows" ]; then + if [ "$OS" = "Windows" ]; then CLI_DIST="arduino-cli_${TAG}_${OS}_${ARCH}.zip" else CLI_DIST="arduino-cli_${TAG}_${OS}_${ARCH}.tar.gz" @@ -172,14 +172,14 @@ testVersion() { set +e CLI="$(which $PROJECT_NAME)" if [ "$?" = "1" ]; then - fail "$PROJECT_NAME not found. Did you add "$LBINDIR" to your "'$PATH?' - fi - if [ $CLI != "$LBINDIR/$PROJECT_NAME" ]; then + echo "$PROJECT_NAME not found. You might want to add "$LBINDIR" to your "'$PATH' + elif [ $CLI != "$LBINDIR/$PROJECT_NAME" ]; then fail "An existing $PROJECT_NAME was found at $CLI. Please prepend "$LBINDIR" to your "'$PATH'" or remove the existing one." fi + set -e - CLI_VERSION=$($PROJECT_NAME version) - echo "$CLI_VERSION installed successfully" + CLI_VERSION=$($LBINDIR/$PROJECT_NAME version) + echo "$CLI_VERSION installed successfully in $LBINDIR" } diff --git a/legacy/builder/builder.go b/legacy/builder/builder.go index 39be76bbafb..98efad782c0 100644 --- a/legacy/builder/builder.go +++ b/legacy/builder/builder.go @@ -43,7 +43,6 @@ import ( "github.com/arduino/arduino-cli/legacy/builder/phases" "github.com/arduino/arduino-cli/legacy/builder/types" "github.com/arduino/arduino-cli/legacy/builder/utils" - "github.com/arduino/go-paths-helper" ) var MAIN_FILE_VALID_EXTENSIONS = map[string]bool{".ino": true, ".pde": true} @@ -58,7 +57,7 @@ type Builder struct{} func (s *Builder) Run(ctx *types.Context) error { if ctx.BuildPath == nil { - ctx.BuildPath = paths.New(bldr.GenBuildPath(ctx.SketchLocation.String())) + ctx.BuildPath = bldr.GenBuildPath(ctx.SketchLocation) } if err := bldr.EnsureBuildPathExists(ctx.BuildPath.String()); err != nil { @@ -150,7 +149,7 @@ type Preprocess struct{} func (s *Preprocess) Run(ctx *types.Context) error { if ctx.BuildPath == nil { - ctx.BuildPath = paths.New(bldr.GenBuildPath(ctx.SketchLocation.String())) + ctx.BuildPath = bldr.GenBuildPath(ctx.SketchLocation) } if err := bldr.EnsureBuildPathExists(ctx.BuildPath.String()); err != nil { @@ -186,7 +185,7 @@ type ParseHardwareAndDumpBuildProperties struct{} func (s *ParseHardwareAndDumpBuildProperties) Run(ctx *types.Context) error { if ctx.BuildPath == nil { - ctx.BuildPath = paths.New(bldr.GenBuildPath(ctx.SketchLocation.String())) + ctx.BuildPath = bldr.GenBuildPath(ctx.SketchLocation) } commands := []types.Command{ diff --git a/legacy/builder/container_find_includes.go b/legacy/builder/container_find_includes.go index 5666676c902..d7c0f90ed25 100644 --- a/legacy/builder/container_find_includes.go +++ b/legacy/builder/container_find_includes.go @@ -108,6 +108,7 @@ package builder import ( "encoding/json" + "fmt" "os" "os/exec" "time" @@ -196,19 +197,28 @@ type includeCacheEntry struct { Includepath *paths.Path } +func (entry *includeCacheEntry) String() string { + return fmt.Sprintf("SourceFile: %s; Include: %s; IncludePath: %s", + entry.Sourcefile, entry.Include, entry.Includepath) +} + +func (entry *includeCacheEntry) Equals(other *includeCacheEntry) bool { + return entry.String() == other.String() +} + type includeCache struct { // Are the cache contents valid so far? valid bool // Index into entries of the next entry to be processed. Unused // when the cache is invalid. next int - entries []includeCacheEntry + entries []*includeCacheEntry } // Return the next cache entry. Should only be called when the cache is // valid and a next entry is available (the latter can be checked with // ExpectFile). Does not advance the cache. -func (cache *includeCache) Next() includeCacheEntry { +func (cache *includeCache) Next() *includeCacheEntry { return cache.entries[cache.next] } @@ -227,9 +237,9 @@ func (cache *includeCache) ExpectFile(sourcefile *paths.Path) { // invalidated, or was already invalid, an entry with the given values // is appended. func (cache *includeCache) ExpectEntry(sourcefile *paths.Path, include string, librarypath *paths.Path) { - entry := includeCacheEntry{Sourcefile: sourcefile, Include: include, Includepath: librarypath} + entry := &includeCacheEntry{Sourcefile: sourcefile, Include: include, Includepath: librarypath} if cache.valid { - if cache.next < len(cache.entries) && cache.Next() == entry { + if cache.next < len(cache.entries) && cache.Next().Equals(entry) { cache.next++ } else { cache.valid = false diff --git a/legacy/builder/container_setup.go b/legacy/builder/container_setup.go index b5d5eef1fc0..01c5f655342 100644 --- a/legacy/builder/container_setup.go +++ b/legacy/builder/container_setup.go @@ -63,19 +63,21 @@ func (s *ContainerSetupHardwareToolsLibsSketchAndProps) Run(ctx *types.Context) } } - // get abs path to sketch - sketchLocation, err := ctx.SketchLocation.Abs() - if err != nil { - return i18n.WrapError(err) - } + if ctx.SketchLocation != nil { + // get abs path to sketch + sketchLocation, err := ctx.SketchLocation.Abs() + if err != nil { + return i18n.WrapError(err) + } - // load sketch - sketch, err := bldr.SketchLoad(sketchLocation.String(), ctx.BuildPath.String()) - if err != nil { - return i18n.WrapError(err) + // load sketch + sketch, err := bldr.SketchLoad(sketchLocation.String(), ctx.BuildPath.String()) + if err != nil { + return i18n.WrapError(err) + } + ctx.SketchLocation = paths.New(sketch.MainFile.Path) + ctx.Sketch = types.SketchToLegacy(sketch) } - ctx.SketchLocation = paths.New(sketch.MainFile.Path) - ctx.Sketch = types.SketchToLegacy(sketch) commands = []types.Command{ &SetupBuildProperties{}, diff --git a/legacy/builder/gohasissues/go_has_issues.go b/legacy/builder/gohasissues/go_has_issues.go index 60c422ac018..4f24c62c0bf 100644 --- a/legacy/builder/gohasissues/go_has_issues.go +++ b/legacy/builder/gohasissues/go_has_issues.go @@ -34,8 +34,6 @@ import ( "os" "path/filepath" "sort" - "strconv" - "strings" ) func Walk(root string, walkFn filepath.WalkFunc) error { @@ -127,19 +125,3 @@ func resolveSymlink(parentFolder string, info os.FileInfo) (os.FileInfo, error) func isSymlink(info os.FileInfo) bool { return info.Mode()&os.ModeSymlink != 0 } - -func Unquote(s string) (string, error) { - if stringStartsEndsWith(s, "'") { - s = s[1 : len(s)-1] - } - - if !stringStartsEndsWith(s, "\"") { - return s, nil - } - - return strconv.Unquote(s) -} - -func stringStartsEndsWith(s string, c string) bool { - return strings.HasPrefix(s, c) && strings.HasSuffix(s, c) -} diff --git a/legacy/builder/grpc/rpc.go b/legacy/builder/grpc/rpc.go index fd38a58f9ca..d135d03900a 100644 --- a/legacy/builder/grpc/rpc.go +++ b/legacy/builder/grpc/rpc.go @@ -57,7 +57,7 @@ type builderServer struct { } func (s *builderServer) watch() { - folders := []paths.PathList{s.ctx.HardwareDirs, s.ctx.ToolsDirs, s.ctx.BuiltInLibrariesDirs, s.ctx.OtherLibrariesDirs} + folders := []paths.PathList{s.ctx.HardwareDirs, s.ctx.BuiltInToolsDirs, s.ctx.BuiltInLibrariesDirs, s.ctx.OtherLibrariesDirs} for _, category := range folders { for _, folder := range category { @@ -84,7 +84,7 @@ func (s *builderServer) DropCache(ctx context.Context, args *pb.VerboseParams) ( func (s *builderServer) Autocomplete(ctx context.Context, args *pb.BuildParams) (*pb.Response, error) { s.ctx.HardwareDirs = paths.NewPathList(strings.Split(args.HardwareFolders, ",")...) - s.ctx.ToolsDirs = paths.NewPathList(strings.Split(args.ToolsFolders, ",")...) + s.ctx.BuiltInToolsDirs = paths.NewPathList(strings.Split(args.ToolsFolders, ",")...) s.ctx.BuiltInLibrariesDirs = paths.NewPathList(strings.Split(args.BuiltInLibrariesFolders, ",")...) s.ctx.OtherLibrariesDirs = paths.NewPathList(strings.Split(args.OtherLibrariesFolders, ",")...) s.ctx.SketchLocation = paths.New(args.SketchLocation) @@ -128,7 +128,7 @@ func (s *builderServer) Autocomplete(ctx context.Context, args *pb.BuildParams) func (s *builderServer) Build(args *pb.BuildParams, stream pb.Builder_BuildServer) error { s.ctx.HardwareDirs = paths.NewPathList(strings.Split(args.HardwareFolders, ",")...) - s.ctx.ToolsDirs = paths.NewPathList(strings.Split(args.ToolsFolders, ",")...) + s.ctx.BuiltInToolsDirs = paths.NewPathList(strings.Split(args.ToolsFolders, ",")...) s.ctx.BuiltInLibrariesDirs = paths.NewPathList(strings.Split(args.BuiltInLibrariesFolders, ",")...) s.ctx.OtherLibrariesDirs = paths.NewPathList(strings.Split(args.OtherLibrariesFolders, ",")...) s.ctx.SketchLocation = paths.New(args.SketchLocation) diff --git a/legacy/builder/phases/core_builder.go b/legacy/builder/phases/core_builder.go index b72a628005b..dc6540f16ad 100644 --- a/legacy/builder/phases/core_builder.go +++ b/legacy/builder/phases/core_builder.go @@ -53,7 +53,13 @@ func (s *CoreBuilder) Run(ctx *types.Context) error { } if coreBuildCachePath != nil { - if err := coreBuildCachePath.MkdirAll(); err != nil { + if _, err := coreBuildCachePath.RelTo(ctx.BuildPath); err != nil { + logger := ctx.GetLogger() + logger.Println(constants.LOG_LEVEL_INFO, "Couldn't deeply cache core build: {0}", err) + logger.Println(constants.LOG_LEVEL_INFO, "Running normal build of the core...") + coreBuildCachePath = nil + ctx.CoreBuildCachePath = nil + } else if err := coreBuildCachePath.MkdirAll(); err != nil { return i18n.WrapError(err) } } diff --git a/legacy/builder/print_used_and_not_used_libraries.go b/legacy/builder/print_used_and_not_used_libraries.go index 81ab07ee095..d4922355b43 100644 --- a/legacy/builder/print_used_and_not_used_libraries.go +++ b/legacy/builder/print_used_and_not_used_libraries.go @@ -58,6 +58,9 @@ func (s *PrintUsedAndNotUsedLibraries) Run(ctx *types.Context) error { libraryResolutionResults := ctx.LibrariesResolutionResults for header, libResResult := range libraryResolutionResults { + if len(libResResult.NotUsedLibraries) == 0 { + continue + } logger.Fprintln(os.Stdout, logLevel, constants.MSG_LIBRARIES_MULTIPLE_LIBS_FOUND_FOR, header) logger.Fprintln(os.Stdout, logLevel, constants.MSG_LIBRARIES_USED, libResResult.Library.InstallDir) for _, notUsedLibrary := range libResResult.NotUsedLibraries { diff --git a/legacy/builder/setup_build_properties.go b/legacy/builder/setup_build_properties.go index 7ae9a54a3fd..65718bfa6b6 100644 --- a/legacy/builder/setup_build_properties.go +++ b/legacy/builder/setup_build_properties.go @@ -127,6 +127,8 @@ func (s *SetupBuildProperties) Run(ctx *types.Context) error { buildProperties.Set("extra.time.zone", strconv.Itoa(timeutils.TimezoneOffsetNoDST(now))) buildProperties.Set("extra.time.dst", strconv.Itoa(timeutils.DaylightSavingsOffset(now))) + buildProperties.Merge(ctx.PackageManager.CustomGlobalProperties) + ctx.BuildProperties = buildProperties return nil diff --git a/legacy/builder/test/builder_test.go b/legacy/builder/test/builder_test.go index 4f2d5f582b1..b7dc59cada9 100644 --- a/legacy/builder/test/builder_test.go +++ b/legacy/builder/test/builder_test.go @@ -242,7 +242,7 @@ func TestBuilderBridgeRedBearLab(t *testing.T) { ctx := prepareBuilderTestContext(t, paths.New("downloaded_libraries", "Bridge", "examples", "Bridge", "Bridge.ino"), "RedBearLab:avr:blend") ctx.HardwareDirs = append(ctx.HardwareDirs, paths.New("downloaded_board_manager_stuff")) - ctx.ToolsDirs = append(ctx.ToolsDirs, paths.New("downloaded_board_manager_stuff")) + ctx.BuiltInToolsDirs = append(ctx.BuiltInToolsDirs, paths.New("downloaded_board_manager_stuff")) buildPath := SetupBuildPath(t, ctx) defer buildPath.RemoveAll() @@ -277,7 +277,7 @@ func TestBuilderSketchNoFunctions(t *testing.T) { ctx := prepareBuilderTestContext(t, paths.New("sketch_no_functions", "main.ino"), "RedBearLab:avr:blend") ctx.HardwareDirs = append(ctx.HardwareDirs, paths.New("downloaded_board_manager_stuff")) - ctx.ToolsDirs = append(ctx.ToolsDirs, paths.New("downloaded_board_manager_stuff")) + ctx.BuiltInToolsDirs = append(ctx.BuiltInToolsDirs, paths.New("downloaded_board_manager_stuff")) buildPath := SetupBuildPath(t, ctx) defer buildPath.RemoveAll() @@ -293,7 +293,7 @@ func TestBuilderSketchWithBackup(t *testing.T) { ctx := prepareBuilderTestContext(t, paths.New("sketch_with_backup_files", "sketch.ino"), "arduino:avr:uno") ctx.HardwareDirs = append(ctx.HardwareDirs, paths.New("downloaded_board_manager_stuff")) - ctx.ToolsDirs = append(ctx.ToolsDirs, paths.New("downloaded_board_manager_stuff")) + ctx.BuiltInToolsDirs = append(ctx.BuiltInToolsDirs, paths.New("downloaded_board_manager_stuff")) buildPath := SetupBuildPath(t, ctx) defer buildPath.RemoveAll() diff --git a/legacy/builder/test/create_build_options_map_test.go b/legacy/builder/test/create_build_options_map_test.go index eabbcec343d..9361ed08265 100644 --- a/legacy/builder/test/create_build_options_map_test.go +++ b/legacy/builder/test/create_build_options_map_test.go @@ -41,7 +41,7 @@ import ( func TestCreateBuildOptionsMap(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList("hardware", "hardware2"), - ToolsDirs: paths.NewPathList("tools"), + BuiltInToolsDirs: paths.NewPathList("tools"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketchLocation"), FQBN: parseFQBN(t, "my:nice:fqbn"), @@ -55,15 +55,15 @@ func TestCreateBuildOptionsMap(t *testing.T) { err := create.Run(ctx) NoError(t, err) - require.Equal(t, "{\n"+ - " \"additionalFiles\": \"\",\n"+ - " \"builtInLibrariesFolders\": \"\",\n"+ - " \"customBuildProperties\": \"\",\n"+ - " \"fqbn\": \"my:nice:fqbn\",\n"+ - " \"hardwareFolders\": \"hardware,hardware2\",\n"+ - " \"otherLibrariesFolders\": \"libraries\",\n"+ - " \"runtime.ide.version\": \"ideVersion\",\n"+ - " \"sketchLocation\": \"sketchLocation\",\n"+ - " \"toolsFolders\": \"tools\"\n"+ - "}", ctx.BuildOptionsJson) + require.Equal(t, `{ + "additionalFiles": "", + "builtInLibrariesFolders": "", + "builtInToolsFolders": "tools", + "customBuildProperties": "", + "fqbn": "my:nice:fqbn", + "hardwareFolders": "hardware,hardware2", + "otherLibrariesFolders": "libraries", + "runtime.ide.version": "ideVersion", + "sketchLocation": "sketchLocation" +}`, ctx.BuildOptionsJson) } diff --git a/legacy/builder/test/load_vid_pid_specific_properties_test.go b/legacy/builder/test/load_vid_pid_specific_properties_test.go index 7ff250207e1..cee99439076 100644 --- a/legacy/builder/test/load_vid_pid_specific_properties_test.go +++ b/legacy/builder/test/load_vid_pid_specific_properties_test.go @@ -44,7 +44,7 @@ func TestLoadVIDPIDSpecificPropertiesWhenNoVIDPIDAreProvided(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools", "./tools_builtin"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools", "./tools_builtin"), SketchLocation: paths.New("sketch1", "sketch.ino"), FQBN: parseFQBN(t, "arduino:avr:micro"), ArduinoAPIVersion: "10600", @@ -74,7 +74,7 @@ func TestLoadVIDPIDSpecificProperties(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools", "./tools_builtin"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools", "./tools_builtin"), SketchLocation: paths.New("sketch1", "sketch.ino"), FQBN: parseFQBN(t, "arduino:avr:micro"), ArduinoAPIVersion: "10600", diff --git a/legacy/builder/test/merge_sketch_with_bootloader_test.go b/legacy/builder/test/merge_sketch_with_bootloader_test.go index 4ad70c8e137..29d6cae0ea6 100644 --- a/legacy/builder/test/merge_sketch_with_bootloader_test.go +++ b/legacy/builder/test/merge_sketch_with_bootloader_test.go @@ -46,7 +46,7 @@ func TestMergeSketchWithBootloader(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools"), BuiltInLibrariesDirs: paths.NewPathList("downloaded_libraries"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketch1", "sketch.ino"), @@ -88,7 +88,7 @@ func TestMergeSketchWithBootloaderSketchInBuildPath(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools"), BuiltInLibrariesDirs: paths.NewPathList("downloaded_libraries"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketch1", "sketch.ino"), @@ -130,7 +130,7 @@ func TestMergeSketchWithBootloaderWhenNoBootloaderAvailable(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools"), BuiltInLibrariesDirs: paths.NewPathList("downloaded_libraries"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketch1", "sketch.ino"), @@ -168,7 +168,7 @@ func TestMergeSketchWithBootloaderPathIsParameterized(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList(filepath.Join("..", "hardware"), "hardware", "downloaded_hardware", "user_hardware"), - ToolsDirs: paths.NewPathList("downloaded_tools"), + BuiltInToolsDirs: paths.NewPathList("downloaded_tools"), BuiltInLibrariesDirs: paths.NewPathList("downloaded_libraries"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketch1", "sketch.ino"), diff --git a/legacy/builder/test/store_build_options_map_test.go b/legacy/builder/test/store_build_options_map_test.go index 79ec8400fb9..0e0296c1bdb 100644 --- a/legacy/builder/test/store_build_options_map_test.go +++ b/legacy/builder/test/store_build_options_map_test.go @@ -42,7 +42,7 @@ import ( func TestStoreBuildOptionsMap(t *testing.T) { ctx := &types.Context{ HardwareDirs: paths.NewPathList("hardware"), - ToolsDirs: paths.NewPathList("tools"), + BuiltInToolsDirs: paths.NewPathList("tools"), BuiltInLibrariesDirs: paths.NewPathList("built-in libraries"), OtherLibrariesDirs: paths.NewPathList("libraries"), SketchLocation: paths.New("sketchLocation"), @@ -73,15 +73,15 @@ func TestStoreBuildOptionsMap(t *testing.T) { bytes, err := buildPath.Join(constants.BUILD_OPTIONS_FILE).ReadFile() NoError(t, err) - require.Equal(t, "{\n"+ - " \"additionalFiles\": \"\",\n"+ - " \"builtInLibrariesFolders\": \"built-in libraries\",\n"+ - " \"customBuildProperties\": \"custom=prop\",\n"+ - " \"fqbn\": \"my:nice:fqbn\",\n"+ - " \"hardwareFolders\": \"hardware\",\n"+ - " \"otherLibrariesFolders\": \"libraries\",\n"+ - " \"runtime.ide.version\": \"ideVersion\",\n"+ - " \"sketchLocation\": \"sketchLocation\",\n"+ - " \"toolsFolders\": \"tools\"\n"+ - "}", string(bytes)) + require.Equal(t, `{ + "additionalFiles": "", + "builtInLibrariesFolders": "built-in libraries", + "builtInToolsFolders": "tools", + "customBuildProperties": "custom=prop", + "fqbn": "my:nice:fqbn", + "hardwareFolders": "hardware", + "otherLibrariesFolders": "libraries", + "runtime.ide.version": "ideVersion", + "sketchLocation": "sketchLocation" +}`, string(bytes)) } diff --git a/legacy/builder/test/unquote_test.go b/legacy/builder/test/unquote_test.go deleted file mode 100644 index 279e21e0a82..00000000000 --- a/legacy/builder/test/unquote_test.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of Arduino Builder. - * - * Arduino Builder is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - * As a special exception, you may use this file as part of a free software - * library without restriction. Specifically, if other files instantiate - * templates or use macros or inline functions from this file, or you compile - * this file and link it with other files to produce an executable, this - * file does not by itself cause the resulting executable to be covered by - * the GNU General Public License. This exception does not however - * invalidate any other reasons why the executable file might be covered by - * the GNU General Public License. - * - * Copyright 2015 Arduino LLC (http://www.arduino.cc/) - */ - -package test - -import ( - "github.com/arduino/arduino-cli/legacy/builder/gohasissues" - "github.com/stretchr/testify/require" - "testing" -) - -func TestStrConvUnquote(t *testing.T) { - s, err := gohasissues.Unquote("ciao") - NoError(t, err) - require.Equal(t, "ciao", s) - - s, err = gohasissues.Unquote("arduino:avr:uno") - NoError(t, err) - require.Equal(t, "arduino:avr:uno", s) - - s, err = gohasissues.Unquote("\"arduino:avr:uno\"") - NoError(t, err) - require.Equal(t, "arduino:avr:uno", s) - - s, err = gohasissues.Unquote("'arduino:avr:uno'") - NoError(t, err) - require.Equal(t, "arduino:avr:uno", s) -} diff --git a/legacy/builder/tools_loader.go b/legacy/builder/tools_loader.go index c7bd8be6469..82b5256f0da 100644 --- a/legacy/builder/tools_loader.go +++ b/legacy/builder/tools_loader.go @@ -30,9 +30,6 @@ package builder import ( - "fmt" - "strings" - "github.com/arduino/arduino-cli/legacy/builder/types" "github.com/arduino/go-paths-helper" ) @@ -45,28 +42,9 @@ func (s *ToolsLoader) Run(ctx *types.Context) error { return nil } - folders := paths.NewPathList() builtinFolders := paths.NewPathList() - - if ctx.BuiltInToolsDirs != nil || len(ctx.BuiltInLibrariesDirs) == 0 { - folders = ctx.ToolsDirs + if ctx.BuiltInToolsDirs != nil { builtinFolders = ctx.BuiltInToolsDirs - } else { - // Auto-detect built-in tools folders (for arduino-builder backward compatibility) - // this is a deprecated feature and will be removed in the future - builtinHardwareFolder, err := ctx.BuiltInLibrariesDirs[0].Join("..").Abs() - - if err != nil { - fmt.Println("Error detecting ") - } - - for _, folder := range ctx.ToolsDirs { - if !strings.Contains(folder.String(), builtinHardwareFolder.String()) { // TODO: make a function to check for subfolders - folders = append(folders, folder) - } else { - builtinFolders = append(builtinFolders, folder) - } - } } pm := ctx.PackageManager diff --git a/legacy/builder/types/context.go b/legacy/builder/types/context.go index 1a8a6b5c788..8238ed98c3f 100644 --- a/legacy/builder/types/context.go +++ b/legacy/builder/types/context.go @@ -24,7 +24,6 @@ type ProgressStruct struct { type Context struct { // Build options HardwareDirs paths.PathList - ToolsDirs paths.PathList BuiltInToolsDirs paths.PathList BuiltInLibrariesDirs paths.PathList OtherLibrariesDirs paths.PathList @@ -122,7 +121,7 @@ type Context struct { func (ctx *Context) ExtractBuildOptions() *properties.Map { opts := properties.NewMap() opts.Set("hardwareFolders", strings.Join(ctx.HardwareDirs.AsStrings(), ",")) - opts.Set("toolsFolders", strings.Join(ctx.ToolsDirs.AsStrings(), ",")) + opts.Set("builtInToolsFolders", strings.Join(ctx.BuiltInToolsDirs.AsStrings(), ",")) opts.Set("builtInLibrariesFolders", strings.Join(ctx.BuiltInLibrariesDirs.AsStrings(), ",")) opts.Set("otherLibrariesFolders", strings.Join(ctx.OtherLibrariesDirs.AsStrings(), ",")) opts.SetPath("sketchLocation", ctx.SketchLocation) @@ -146,7 +145,7 @@ func (ctx *Context) ExtractBuildOptions() *properties.Map { func (ctx *Context) InjectBuildOptions(opts *properties.Map) { ctx.HardwareDirs = paths.NewPathList(strings.Split(opts.Get("hardwareFolders"), ",")...) - ctx.ToolsDirs = paths.NewPathList(strings.Split(opts.Get("toolsFolders"), ",")...) + ctx.BuiltInToolsDirs = paths.NewPathList(strings.Split(opts.Get("builtInToolsFolders"), ",")...) ctx.BuiltInLibrariesDirs = paths.NewPathList(strings.Split(opts.Get("builtInLibrariesFolders"), ",")...) ctx.OtherLibrariesDirs = paths.NewPathList(strings.Split(opts.Get("otherLibrariesFolders"), ",")...) ctx.SketchLocation = opts.GetPath("sketchLocation") diff --git a/test/conftest.py b/test/conftest.py index b34bd5cc6e9..a11e46e2fd0 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -15,7 +15,7 @@ import os import pytest -from invoke import run +from invoke.context import Context @pytest.fixture(scope="function") @@ -38,7 +38,17 @@ def downloads_dir(tmpdir_factory): @pytest.fixture(scope="function") -def run_command(data_dir, downloads_dir): +def working_dir(tmpdir_factory): + """ + A tmp folder to work in + will be created before running each test and deleted + at the end, this way all the tests work in isolation. + """ + return str(tmpdir_factory.mktemp("ArduinoTestWork")) + + +@pytest.fixture(scope="function") +def run_command(pytestconfig, data_dir, downloads_dir, working_dir): """ Provide a wrapper around invoke's `run` API so that every test will work in the same temporary folder. @@ -46,7 +56,7 @@ def run_command(data_dir, downloads_dir): Useful reference: http://docs.pyinvoke.org/en/1.2/api/runners.html#invoke.runners.Result """ - cli_path = os.path.join(pytest.config.rootdir, "..", "arduino-cli") + cli_path = os.path.join(pytestconfig.rootdir, "..", "arduino-cli") env = { "ARDUINO_DATA_DIR": data_dir, "ARDUINO_DOWNLOADS_DIR": downloads_dir, @@ -55,6 +65,8 @@ def run_command(data_dir, downloads_dir): def _run(cmd_string): cli_full_line = "{} {}".format(cli_path, cmd_string) - return run(cli_full_line, echo=False, hide=True, warn=True, env=env) + run_context = Context() + with run_context.cd(working_dir): + return run_context.run(cli_full_line, echo=False, hide=True, warn=True, env=env) return _run diff --git a/test/requirements.txt b/test/requirements.txt index f83a20e3198..fa12ab55097 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -2,7 +2,8 @@ astroid==2.2.5 atomicwrites==1.3.0 attrs==19.1.0 importlib-metadata==0.18 -invoke==1.2.0 +# temporary, replaces invoke==1.3.0 in favour of https://github.com/pyinvoke/invoke/pull/661 +git+https://github.com/flazzarini/invoke.git isort==4.3.21 lazy-object-proxy==1.4.1 mccabe==0.6.1 @@ -13,7 +14,8 @@ pluggy==0.12.0 py==1.8.0 pylint==2.3.1 pyparsing==2.4.0 -pytest==5.0.1 +pyserial==3.4 +pytest==5.1.3 semver==2.8.1 simplejson==3.16.0 six==1.12.0 diff --git a/test/test_board.py b/test/test_board.py index e1b9636cd5b..7bc135d69bc 100644 --- a/test/test_board.py +++ b/test/test_board.py @@ -44,7 +44,15 @@ def test_core_listall(run_command): def test_core_search(run_command): url = "https://raw.githubusercontent.com/arduino/arduino-cli/master/test/testdata/test_index.json" assert run_command("core update-index --additional-urls={}".format(url)) - # default search + # list all + result = run_command("core search") + assert result.ok + assert 3 < len(result.stdout.splitlines()) + result = run_command("core search --format json") + assert result.ok + data = json.loads(result.stdout) + assert 1 < len(data) + # search a specific core result = run_command("core search avr") assert result.ok assert 2 < len(result.stdout.splitlines()) diff --git a/test/test_compile.py b/test/test_compile.py index a7c16f197ab..ad15f20f719 100644 --- a/test/test_compile.py +++ b/test/test_compile.py @@ -14,6 +14,7 @@ # a commercial license, send an email to license@arduino.cc. import json import os + import pytest from .common import running_on_ci @@ -38,7 +39,7 @@ def test_compile_with_simple_sketch(run_command, data_dir): result = run_command("core update-index") assert result.ok - # # Download latest AVR + # Download latest AVR result = run_command("core install arduino:avr") assert result.ok @@ -47,7 +48,7 @@ def test_compile_with_simple_sketch(run_command, data_dir): fqbn = "arduino:avr:uno" # Create a test sketch - result = run_command("sketch new {}".format(sketch_name)) + result = run_command("sketch new {}".format(sketch_path)) assert result.ok assert "Sketch created in: {}".format(sketch_path) in result.stdout @@ -69,6 +70,62 @@ def test_compile_with_simple_sketch(run_command, data_dir): assert is_message_sequence_in_json_log_traces(expected_trace_sequence, json_log_lines) +def test_compile_with_sketch_with_symlink_selfloop(run_command, data_dir): + # Init the environment explicitly + result = run_command("core update-index") + assert result.ok + + # Download latest AVR + result = run_command("core install arduino:avr") + assert result.ok + + sketch_name = "CompileIntegrationTestSymlinkSelfLoop" + sketch_path = os.path.join(data_dir, sketch_name) + fqbn = "arduino:avr:uno" + + # Create a test sketch + result = run_command("sketch new {}".format(sketch_path)) + assert result.ok + assert "Sketch created in: {}".format(sketch_path) in result.stdout + + # create a symlink that loops on himself + loop_file_path = os.path.join(sketch_path, "loop") + os.symlink(loop_file_path, loop_file_path) + + # Build sketch for arduino:avr:uno + result = run_command( + "compile -b {fqbn} {sketch_path}".format( + fqbn=fqbn, sketch_path=sketch_path)) + # The assertion is a bit relaxed in this case because win behaves differently from macOs and linux + # returning a different error detailed message + assert "Error during sketch processing" in result.stderr + assert not result.ok + + sketch_name = "CompileIntegrationTestSymlinkDirLoop" + sketch_path = os.path.join(data_dir, sketch_name) + fqbn = "arduino:avr:uno" + + # Create a test sketch + result = run_command("sketch new {}".format(sketch_path)) + assert result.ok + assert "Sketch created in: {}".format(sketch_path) in result.stdout + + # create a symlink that loops on the upper level + loop_dir_path = os.path.join(sketch_path, "loop_dir") + os.mkdir(loop_dir_path) + loop_dir_symlink_path = os.path.join(loop_dir_path, "loop_dir_symlink") + os.symlink(loop_dir_path, loop_dir_symlink_path) + + # Build sketch for arduino:avr:uno + result = run_command( + "compile -b {fqbn} {sketch_path}".format( + fqbn=fqbn, sketch_path=sketch_path)) + # The assertion is a bit relaxed also in this case because macOS behaves differently from win and linux: + # the cli does not follow recursively the symlink til breaking + assert "Error during sketch processing" in result.stderr + assert not result.ok + + @pytest.mark.skipif(running_on_ci(), reason="VMs have no serial ports") def test_compile_and_compile_combo(run_command, data_dir): # Init the environment explicitly @@ -83,7 +140,7 @@ def test_compile_and_compile_combo(run_command, data_dir): # Create a test sketch sketch_name = "CompileAndUploadIntegrationTest" sketch_path = os.path.join(data_dir, sketch_name) - result = run_command("sketch new CompileAndUploadIntegrationTest") + result = run_command("sketch new {}".format(sketch_path)) assert result.ok assert "Sketch created in: {}".format(sketch_path) in result.stdout @@ -124,7 +181,7 @@ def test_compile_and_compile_combo(run_command, data_dir): # Build sketch for each detected board for board in detected_boards: - log_file_name = "{fqbn}-compile.log".format(fqbn=board.get('fqbn')) + log_file_name = "{fqbn}-compile.log".format(fqbn=board.get('fqbn').replace(":", "-")) log_file_path = os.path.join(data_dir, log_file_name) command_log_flags = "--log-format json --log-file {} --log-level trace".format(log_file_path) result = run_command("compile -b {fqbn} --upload -p {address} {sketch_path} {log_flags}".format( diff --git a/test/test_lib.py b/test/test_lib.py index a899275a1ad..22f7fbff525 100644 --- a/test/test_lib.py +++ b/test/test_lib.py @@ -19,15 +19,15 @@ def test_list(run_command): # Init the environment explicitly assert run_command("core update-index") - # When ouput is empty, nothing is printed out, no matter the output format + # When output is empty, nothing is printed out, no matter the output format result = run_command("lib list") assert result.ok assert "" == result.stderr - assert "" == result.stdout + assert "No libraries installed." == result.stdout.strip() result = run_command("lib list --format json") assert result.ok assert "" == result.stderr - assert "" == result.stdout + assert "null" == result.stdout # Install something we can list at a version older than latest result = run_command("lib install ArduinoJson@6.11.0") @@ -83,7 +83,7 @@ def test_search(run_command): result = run_command("lib search --names") assert result.ok - out_lines = result.stdout.splitlines() + out_lines = result.stdout.strip().splitlines() # Create an array with just the name of the vars libs = [] for line in out_lines: diff --git a/test/test_sketch.py b/test/test_sketch.py new file mode 100644 index 00000000000..e59a5e51611 --- /dev/null +++ b/test/test_sketch.py @@ -0,0 +1,58 @@ +# This file is part of arduino-cli. +# +# Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +# +# This software is released under the GNU General Public License version 3, +# which covers the main part of arduino-cli. +# The terms of this license can be found at: +# https://www.gnu.org/licenses/gpl-3.0.en.html +# +# You can be released from the requirements of the above licenses by purchasing +# a commercial license. Buying such a license is mandatory if you want to modify or +# otherwise use the software for commercial activities involving the Arduino +# software without disclosing the source code of your own applications. To purchase +# a commercial license, send an email to license@arduino.cc. +import os +import platform + +import pytest + +from test.common import running_on_ci + + +@pytest.mark.skipif(running_on_ci() and platform.system() == "Windows", + reason="Test disabled on Github Actions Win VM until tmpdir inconsistent behavior bug is fixed") +def test_sketch_new(run_command, working_dir): + # Create a test sketch in current directory + current_path = working_dir + sketch_name = "SketchNewIntegrationTest" + current_sketch_path = os.path.join(current_path, sketch_name) + result = run_command("sketch new {}".format(sketch_name)) + assert result.ok + assert "Sketch created in: {}".format(current_sketch_path) in result.stdout + assert os.path.isfile(os.path.join(current_sketch_path, sketch_name + ".ino")) + + # Create a test sketch in current directory but using an absolute path + sketch_name = "SketchNewIntegrationTestAbsolute" + current_sketch_path = os.path.join(current_path, sketch_name) + result = run_command("sketch new {}".format(current_sketch_path)) + assert result.ok + assert "Sketch created in: {}".format(current_sketch_path) in result.stdout + assert os.path.isfile(os.path.join(current_sketch_path, sketch_name + ".ino")) + + # Create a test sketch in current directory subpath but using an absolute path + sketch_name = "SketchNewIntegrationTestSubpath" + sketch_subpath = os.path.join("subpath", sketch_name) + current_sketch_path = os.path.join(current_path, sketch_subpath) + result = run_command("sketch new {}".format(sketch_subpath)) + assert result.ok + assert "Sketch created in: {}".format(current_sketch_path) in result.stdout + assert os.path.isfile(os.path.join(current_sketch_path, sketch_name + ".ino")) + + # Create a test sketch in current directory using .ino extension + sketch_name = "SketchNewIntegrationTestDotIno" + current_sketch_path = os.path.join(current_path, sketch_name) + result = run_command("sketch new {}".format(sketch_name + ".ino")) + assert result.ok + assert "Sketch created in: {}".format(current_sketch_path) in result.stdout + assert os.path.isfile(os.path.join(current_sketch_path, sketch_name + ".ino")) diff --git a/version/version.go b/version/version.go index 22700de2614..877245969b3 100644 --- a/version/version.go +++ b/version/version.go @@ -22,7 +22,7 @@ import ( ) var ( - defaultVersionString = "0.3.7-alpha.preview" + defaultVersionString = "0.0.0-git" versionString = "" commit = "" )