diff --git a/.editorconfig b/.editorconfig index 6cf872901..8d4baf413 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,8 +10,8 @@ indent_style = space [*.{csproj,vcxproj,vcxproj.filters,proj,projitems,shproj,wxs}] indent_size = 2 -# XML config files -[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vstemplate,vsct}] +# Config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vstemplate,vsct,json}] indent_size = 2 # HTML / CSS files diff --git a/.github/workflows/Bonsai.yml b/.github/workflows/Bonsai.yml new file mode 100644 index 000000000..ec5a982a3 --- /dev/null +++ b/.github/workflows/Bonsai.yml @@ -0,0 +1,461 @@ +# ======================================================================================================================================================================= +# Bonsai CI Infrastructure +# ======================================================================================================================================================================= +# When updating this workflow, be mindful that the `latest` tag must also build successfully under this workflow in order to determine which packages have changed. +name: Bonsai +on: + push: + # This prevents tag pushes from triggering this workflow + branches: ['*'] + pull_request: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "Version" + default: "" + will_publish_packages: + description: "Publish packages?" + default: "false" +env: + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + ContinuousIntegrationBuild: true +jobs: + # ===================================================================================================================================================================== + # Determine build matrix + # ===================================================================================================================================================================== + # Some of the build matrix targets are conditional, and `jobs..if` is evaluated before `jobs..strategy.matrix` + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idif + # As such we build the matrix programmatically in its own job and feed it into `build-and-test`. + create-build-matrix: + name: Create Build Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{steps.create-matrix.outputs.matrix}} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Create build matrix + id: create-matrix + run: python .github/workflows/create-build-matrix.py + + # ===================================================================================================================================================================== + # Build, test, and package + # ===================================================================================================================================================================== + build-and-test: + needs: create-build-matrix + strategy: + fail-fast: false + matrix: ${{fromJSON(needs.create-build-matrix.outputs.matrix)}} + name: ${{matrix.platform.name}} ${{matrix.configuration}} + runs-on: ${{matrix.platform.os}} + env: + IsReferenceDummyBuild: ${{matrix.dummy-build}} + UseRepackForBootstrapperPackage: ${{matrix.collect-packages && !matrix.dummy-build}} + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{matrix.checkout-ref}} + + # ----------------------------------------------------------------------- Setup tools + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # Legacy .NET command line tools are only used for building the installer + - name: Setup MSBuild command line + if: matrix.create-installer + uses: microsoft/setup-msbuild@v2 + - name: Setup NuGet command line + if: matrix.create-installer + uses: NuGet/setup-nuget@v2 + with: + nuget-version: 6.x + + # ----------------------------------------------------------------------- Configure build + - name: Configure build + run: python .github/workflows/configure-build.py + env: + github_event_name: ${{github.event_name}} + github_ref: ${{github.ref}} + github_run_number: ${{github.run_number}} + release_is_prerelease: ${{github.event.release.prerelease}} + release_version: ${{github.event.release.tag_name}} + workflow_dispatch_version: ${{github.event.inputs.version}} + workflow_dispatch_will_publish_packages: ${{github.event.inputs.will_publish_packages}} + + # ----------------------------------------------------------------------- Build + - name: Restore + run: dotnet restore Bonsai.sln + + - name: Build + run: dotnet build Bonsai.sln --no-restore --configuration ${{matrix.configuration}} + + # ----------------------------------------------------------------------- Repack bootstrapper + # This happens before pack since the bootstrapper package uses it + - name: Repack bootstrapper + if: matrix.create-installer || env.UseRepackForBootstrapperPackage == 'true' + run: dotnet build Bonsai --no-restore --configuration ${{matrix.configuration}} -t:Repack + + # ----------------------------------------------------------------------- Pack + # Since packages are core to Bonsai functionality we always pack them even if they won't be collected + - name: Pack + id: pack + run: dotnet pack Bonsai.sln --no-restore --no-build --configuration ${{matrix.configuration}} + + # ----------------------------------------------------------------------- Test + - name: Test + if: '!matrix.dummy-build' + run: dotnet test Bonsai.sln --no-restore --no-build --configuration ${{matrix.configuration}} --verbosity normal + + # ----------------------------------------------------------------------- Create portable zip + - name: Create portable zip + id: create-portable-zip + if: matrix.create-installer + run: python .github/workflows/create-portable-zip.py artifacts/Bonsai.zip ${{matrix.configuration}} + env: + NUGET_API_URL: ${{vars.NUGET_API_URL}} + # This should be kept in sync with publish-packages-nuget-org + IS_FULL_RELEASE: ${{github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.will_publish_packages == 'true' && github.event.inputs.version != '')}} + + # ----------------------------------------------------------------------- Build Visual Studio templates + - name: Build Visual Studio templates + if: matrix.create-installer + run: msbuild /nologo /maxCpuCount Bonsai.Templates/Bonsai.Templates.sln /p:Configuration=${{matrix.configuration}} /p:DeployExtension=false + + # ----------------------------------------------------------------------- Build setup + - name: Restore setup + if: matrix.create-installer + # Restoring the packages.config directly means we don't rely on the system-wide Wix install + run: | + nuget restore Bonsai.Setup/packages.config -SolutionDir . + nuget restore Bonsai.Setup.Bootstrapper/packages.config -SolutionDir . + + - name: Build Setup + id: create-installer + if: matrix.create-installer + run: msbuild /nologo /maxCpuCount Bonsai.Setup.sln /p:Configuration=${{matrix.configuration}} + + # ----------------------------------------------------------------------- Collect artifacts + - name: Collect NuGet packages + uses: actions/upload-artifact@v4 + if: matrix.collect-packages && steps.pack.outcome == 'success' && always() + with: + name: Packages${{matrix.artifacts-suffix}} + if-no-files-found: error + path: artifacts/package/${{matrix.configuration-lower}}/** + + - name: Collect portable zip + uses: actions/upload-artifact@v4 + if: steps.create-portable-zip.outcome == 'success' && always() + with: + name: PortableZip${{matrix.artifacts-suffix}} + if-no-files-found: error + path: artifacts/Bonsai.zip + + - name: Collect installer + uses: actions/upload-artifact@v4 + if: steps.create-installer.outcome == 'success' && always() + with: + name: Installer${{matrix.artifacts-suffix}} + if-no-files-found: error + path: artifacts/bin/Bonsai.Setup.Bootstrapper/${{matrix.configuration-lower}}-x86/** + + # ===================================================================================================================================================================== + # Determine which packages need to be published + # ===================================================================================================================================================================== + determine-changed-packages: + name: Detect changed packages + runs-on: ubuntu-latest + # We technically only need the dummy build jobs, but GitHub Actions lacks the ability to depend on specific jobs in a matrix + needs: build-and-test + if: github.event_name != 'pull_request' + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + + # ----------------------------------------------------------------------- Setup tools + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # ----------------------------------------------------------------------- Download packages for comparison + - name: Download packages for comparison + uses: actions/download-artifact@v4 + with: + pattern: Packages* + path: artifacts + + # ----------------------------------------------------------------------- Compare packages + - name: Compare packages + id: compare-packages + run: python .github/workflows/compare-nuget-packages.py artifacts/Packages-dummy-prev/ artifacts/Packages-dummy-next/ artifacts/Packages/ artifacts/ReleaseManifest + + # ----------------------------------------------------------------------- Collect release manifest + - name: Collect release manifest + uses: actions/upload-artifact@v4 + if: steps.compare-packages.outcome != 'skipped' && always() + with: + name: ReleaseManifest + if-no-files-found: error + path: artifacts/ReleaseManifest + + # ===================================================================================================================================================================== + # Publish to GitHub + # ===================================================================================================================================================================== + publish-github: + name: Publish to GitHub + runs-on: ubuntu-latest + permissions: + # Needed to attach files to releases + contents: write + # Needed to upload to GitHub Packages + packages: write + needs: [build-and-test, determine-changed-packages] + # Pushes always publish CI packages (configure-build.py will add the branch name to the version string for branches besides main) + # Published releases always publish packages + # A manual workflow only publishes packages if explicitly enabled + if: github.event_name == 'push' || github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.will_publish_packages == 'true') + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + sparse-checkout: .github + sparse-checkout-cone-mode: false + + # ----------------------------------------------------------------------- Setup tools + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # ----------------------------------------------------------------------- Download artifacts + - name: Download built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: Packages + + - name: Download release manifest + uses: actions/download-artifact@v4 + with: + name: ReleaseManifest + + - name: Download portable zip + uses: actions/download-artifact@v4 + if: github.event_name == 'release' + with: + name: PortableZip + + - name: Download installer + uses: actions/download-artifact@v4 + if: github.event_name == 'release' + with: + name: Installer + + # ----------------------------------------------------------------------- Filter NuGet packages + - name: Filter NuGet packages + run: python .github/workflows/filter-release-packages.py ReleaseManifest Packages + + # ----------------------------------------------------------------------- Upload release assets + - name: Upload release assets + if: github.event_name == 'release' + run: gh release upload ${{github.event.release.tag_name}} Bonsai.zip Bonsai-*.exe --clobber + env: + GH_TOKEN: ${{github.token}} + + # ----------------------------------------------------------------------- Push to GitHub Packages + - name: Push to GitHub Packages + run: dotnet nuget push "Packages/*.nupkg" --skip-duplicate --no-symbols --api-key ${{secrets.GITHUB_TOKEN}} --source https://nuget.pkg.github.com/${{github.repository_owner}} + env: + # This is a workaround for https://github.com/NuGet/Home/issues/9775 + DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER: 0 + + # ===================================================================================================================================================================== + # Publish NuGet Packages to NuGet.org + # ===================================================================================================================================================================== + publish-packages-nuget-org: + name: Publish to NuGet.org + runs-on: ubuntu-latest + environment: PublicRelease + needs: [build-and-test, determine-changed-packages] + # Release builds always publish packages to NuGet.org + # Workflow dispatch builds will only publish packages if enabled and an explicit version number is given + # This should be kept in sync with create-portable-zip + if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.will_publish_packages == 'true' && github.event.inputs.version != '') + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + sparse-checkout: .github + sparse-checkout-cone-mode: false + + # ----------------------------------------------------------------------- Setup tools + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # ----------------------------------------------------------------------- Download artifacts + - name: Download built packages + uses: actions/download-artifact@v4 + with: + name: Packages + path: Packages + + - name: Download release manifest + uses: actions/download-artifact@v4 + with: + name: ReleaseManifest + + # ----------------------------------------------------------------------- Filter NuGet packages + - name: Filter NuGet packages + run: python .github/workflows/filter-release-packages.py ReleaseManifest Packages + + # ----------------------------------------------------------------------- Push to NuGet.org + - name: Push to NuGet.org + run: dotnet nuget push "Packages/*.nupkg" --api-key ${{secrets.NUGET_API_KEY}} --source ${{vars.NUGET_API_URL}} + env: + # This is a workaround for https://github.com/NuGet/Home/issues/9775 + DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER: 0 + + # ----------------------------------------------------------------------- Dispatch docs repo update + # This might seem like an odd spot to do this, but we need access to the PublicRelease environment for our secrets and using it in two jobs means two approvals are needed + - name: Update version and submodule in ${{vars.DOCS_REPO}} + if: github.event_name == 'release' && github.event.release.prerelease == false + run: gh workflow run --repo "$DOCS_REPO" version-bump.yml --raw-field "project=$PROJECT" --raw-field "version=$VERSION" --raw-field "project-fork-url=$PROJECT_FORK_URL" + env: + GH_TOKEN: ${{secrets.DOCS_UPDATE_GH_TOKEN}} + DOCS_REPO: ${{vars.DOCS_REPO}} + PROJECT: bonsai + VERSION: ${{github.event.release.tag_name}} + PROJECT_FORK_URL: ${{github.server_url}}/${{github.repository}}.git + + # ===================================================================================================================================================================== + # Finish up release + # ===================================================================================================================================================================== + finish-up-release: + name: Finish up release + runs-on: ubuntu-latest + permissions: + # Needed to bump tags and version numbers + contents: write + # Needed to close milestones + issues: write + # It's important that this only happens when everything else happened successfully + # Otherwise it's possible for a partial release to occur and re-running the job wouldn't be possible due to the latest tag having already moved + # (This is also why this happens as its own job, it lets us ensure both publish paths happened successfully.) + needs: [publish-github, publish-packages-nuget-org] + # Only do finishing tasks when we're explicitly releasing + if: github.event_name == 'release' && github.event.release.prerelease == false + steps: + # ----------------------------------------------------------------------- Checkout + - name: Checkout + uses: actions/checkout@v4 + with: + ref: refs/heads/main + + # ----------------------------------------------------------------------- Update `latest` tag + - name: Update `latest` tag + run: git push --force origin ${{github.sha}}:refs/tags/latest + + # ----------------------------------------------------------------------- Verify the release happened from the main branch, otherwise skip the version bump + # Bumping the version number has to commit to some branch, but releases happen from tags rather than commits. Therefore we just commit to main + # However bumping the version on main would not be appropriate if we were releasing from another branch (such as a backport branch) or even a tag on a loose commit + # As such we skip bumping the version if we didn't release from the same revision as main. This is fine as bumping the version number is not a critical part of our workflow + # (This has the unintentional side-effect of skipping the version bump when PRs are merged during the workflow run, but we don't expect this to happen) + - name: Get the revision of the main branch + id: main-revision + run: python .github/workflows/gha.py set_output sha `git rev-parse refs/heads/main` + - name: Warn if it doesn't match the release + if: steps.main-revision.outputs.sha != github.sha + run: python .github/workflows/gha.py print_warning "The main branch is at ${{steps.main-revision.outputs.sha}} but the release was made from ${{github.sha}}, the version number will not be automatically bumped." + + # ----------------------------------------------------------------------- Bump version number + - name: Bump version number + if: steps.main-revision.outputs.sha == github.sha + run: | + python .github/workflows/bump-version.py + git add $version_file_path + env: + version_file_path: tooling/CurrentVersion.props + just_released_version: ${{github.event.release.tag_name}} + + # ----------------------------------------------------------------------- Commit and push changes + - name: Commit changes + if: steps.main-revision.outputs.sha == github.sha + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "Bump to version \`$NEXT_VERSION\`" --allow-empty + + - name: Push changes + if: steps.main-revision.outputs.sha == github.sha + run: git push + + # ----------------------------------------------------------------------- Close milestone + - name: Close milestone + uses: actions/github-script@v7 + if: always() + continue-on-error: true + env: + MILESTONE_NAME: ${{github.event.release.tag_name}} + with: + user-agent: actions/github-script for ${{github.repository}} + script: | + const milestoneToClose = process.env.MILESTONE_NAME; + + response = await github.rest.issues.listMilestones({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100, + }); + milestones = response.data; + + for (let milestone of milestones) { + if (milestone.title != milestoneToClose) { + continue; + } + + core.info(`Closing milestone '${milestoneToClose}' #${milestone.number}...`); + await github.rest.issues.updateMilestone({ + owner: context.repo.owner, + repo: context.repo.repo, + milestone_number: milestone.number, + state: 'closed', + }); + + return; + } + + core.warning(`Could not find any milestone associated with '${milestoneToClose}', the milestone for this release will not be closed.`); diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d8d0c44cd..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Build - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - branches: [ main ] - -jobs: - build: - name: Build Bootstrapper - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v2.3.4 - - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 - - - name: Setup VSTest - uses: darenm/Setup-VSTest@v1 - - - name: Setup NuGet - uses: NuGet/setup-nuget@v1.0.5 - - - name: Restore NuGet Packages - run: nuget restore Bonsai.sln - - - name: Build Bonsai - run: msbuild Bonsai.sln /t:Bonsai /p:Configuration=Release - - - name: Build Bonsai32 - run: msbuild Bonsai.sln /t:Bonsai32 /p:Configuration=Release - - - name: Build Tests - run: msbuild Bonsai.sln /t:Bonsai_Core_Tests /t:Bonsai_System_Tests /t:Bonsai_Editor_Tests /p:Configuration=Release - - - name: Run Tests - run: msbuild Bonsai.Tests.proj - - installer: - name: Build Installer - needs: build - if: github.event_name == 'workflow_dispatch' - runs-on: windows-latest - - steps: - - name: Build Templates - run: msbuild Bonsai.Templates/Bonsai.Templates.sln /p:Configuration=Release - - - name: Setup WiX - run: nuget restore Bonsai.Setup.sln - - - name: Build Installer - run: msbuild Bonsai.Setup.sln /p:Configuration=Release \ No newline at end of file diff --git a/.github/workflows/bump-version.py b/.github/workflows/bump-version.py new file mode 100755 index 000000000..8bc52f6cd --- /dev/null +++ b/.github/workflows/bump-version.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import os + +import gha +import nuget + +#================================================================================================== +# Get inputs +#================================================================================================== +def get_environment_variable(name): + ret = os.getenv(name) + + if ret is None or ret == '': + gha.print_error(f"Missing required parameter '{name}'") + return '' + + return ret + +version_file_path = get_environment_variable('version_file_path') +just_released_version = get_environment_variable('just_released_version').strip('v') + +if not nuget.is_valid_version(just_released_version): + gha.print_error('The specified just-released version is not a valid semver version.') + +gha.fail_if_errors() + +#================================================================================================== +# Bump version number +#================================================================================================== + +version = nuget.get_version_parts(just_released_version) +version.patch += 1 +version.prerelease = None +version.build_metadata = None + +print(f"Bumping to version {version}") + +with open(version_file_path, 'w') as f: + f.write("\n") + f.write("\n") + f.write(" \n") + f.write(f" {version}\n") + f.write(" \n") + f.write("") + +gha.set_environment_variable('NEXT_VERSION', str(version)) + +gha.fail_if_errors() diff --git a/.github/workflows/compare-nuget-packages.py b/.github/workflows/compare-nuget-packages.py new file mode 100755 index 000000000..47317f8c1 --- /dev/null +++ b/.github/workflows/compare-nuget-packages.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +import hashlib +import os +import sys + +from pathlib import Path +from zipfile import ZipFile, ZipInfo + +import gha +import nuget + +# Symbol packages will change even for changes that we don't care about because the deterministic hash embedded in the PDB +# is affected by the MVID of a package's dependencies. We don't want to release a new package when the only things that +# changed were external to the package, so we don't check them. +CHECK_SYMBOL_PACKAGES = False + +# The following packages will always release no matter what +always_release_packages = set([ + 'Bonsai', + 'Bonsai.Core', + 'Bonsai.Design', + 'Bonsai.Editor', + 'Bonsai.Player', +]) + +if len(sys.argv) != 5: + gha.print_error('Usage: compare-nuget-packages.py ') + sys.exit(1) +else: + previous_packages_path = Path(sys.argv[1]) + next_packages_path = Path(sys.argv[2]) + release_packages_path = Path(sys.argv[3]) + release_manifest_path = Path(sys.argv[4]) + +if not previous_packages_path.exists(): + gha.print_error(f"Previous packages path '{previous_packages_path}' does not exist.") +if not next_packages_path.exists(): + gha.print_error(f"Next packages path '{next_packages_path}' does not exist.") +if not release_packages_path.exists(): + gha.print_error(f"Release packages path '{previous_packages_path}' does not exist.") +if release_manifest_path.exists(): + gha.print_error(f"Release manifest '{release_manifest_path}' already exists.") +gha.fail_if_errors() + +def verbose_log(message: str): + gha.print_debug(message) + +def should_ignore(file: ZipInfo) -> bool: + # Ignore metadata files which change on every pack + if file.filename == '_rels/.rels': + return True + if file.filename.startswith('package/services/metadata/core-properties/') and file.filename.endswith('.psmdcp'): + return True + + # Don't care about explicit directories + if file.is_dir(): + return True + + return False + +def nuget_packages_are_equivalent(a_path: Path, b_path: Path, is_snupkg: bool = False) -> bool: + verbose_log(f"Comparing '{a_path}' and '{b_path}'") + + # One package exists and the other does not + if a_path.exists() != b_path.exists(): + verbose_log(f"Not equivalent: Only one package actually exists") + return False + + # The package doesn't exist at all, assume mistake unless we're checking the optional symbol packages + if not a_path.exists(): + if is_snupkg: + verbose_log("Equivalent: Neither package exists") + return True + raise FileNotFoundError(f"Neither package exists: '{a_path}' or '{b_path}'") + + # From this point on: Check everything and emit messages for debugging purposes + is_equivalent = True + + # Check if corresponding symbol packages are equivalent + if CHECK_SYMBOL_PACKAGES and not is_snupkg: + if not nuget_packages_are_equivalent(a_path.with_suffix(".snupkg"), b_path.with_suffix(".snupkg"), True): + verbose_log("Not equivalent: Symbol packages are not equivalent") + is_equivalent = False + else: + verbose_log("Symbol packages are equivalent") + + # Compare the contents of the packages + # NuGet package packing is unfortunately not fully deterministic so we cannot compare the packages directly + # https://github.com/NuGet/Home/issues/8601 + with ZipFile(a_path, 'r') as a_zip, ZipFile(b_path, 'r') as b_zip: + b_infos = { } + for b_info in b_zip.infolist(): + if should_ignore(b_info): + continue + assert b_info.filename not in b_infos + b_infos[b_info.filename] = b_info + + for a_info in a_zip.infolist(): + if should_ignore(a_info): + continue + + b_info = b_infos.pop(a_info.filename, None) + if b_info is None: + verbose_log(f"Not equivalent: '{a_info.filename}' exists in '{a_path}' but not in '{b_path}'") + is_equivalent = False + continue + + if a_info.CRC != b_info.CRC: + verbose_log(f"Not equivalent: CRCs of '{a_info.filename}' do not match between '{a_path}' and '{b_path}'") + is_equivalent = False + continue + + if a_info.file_size != b_info.file_size: + verbose_log(f"Not equivalent: File sizes of '{a_info.filename}' do not match between '{a_path}' and '{b_path}'") + is_equivalent = False + continue + + a_hash = hashlib.file_digest(a_zip.open(a_info), 'sha256').hexdigest() # type: ignore + b_hash = hashlib.file_digest(b_zip.open(b_info), 'sha256').hexdigest() # type: ignore + if a_hash != b_hash: + verbose_log(f"Not equivalent: SHA256 hashes of '{a_info.filename}' do not match between '{a_path}' and '{b_path}'") + is_equivalent = False + continue + + # Ensure every file in B was processed + if len(b_infos) > 0: + is_equivalent = False + verbose_log(f"Not equivalent: The following file(s) exist in '{a_path}' but not in '{b_path}'") + for filename in b_infos: + verbose_log(f" '{filename}'") + + return is_equivalent + +different_packages = [] +force_released_packages = [] +next_packages = set() +for file in os.listdir(next_packages_path): + if not file.endswith(".nupkg"): + continue + + # We don't tolerate build metadata here because the nuget_packages_are_equivalent call doesn't either + if not file.endswith(".99.99.99.nupkg"): + gha.print_error(f"Package '{file}' does not have a dummy version.") + + package_name = nuget.get_package_name(file) + next_packages.add(package_name) + + if not nuget_packages_are_equivalent(next_packages_path / file, previous_packages_path / file): + verbose_log(f"'{file}' differs") + different_packages.append(package_name) + elif package_name in always_release_packages: + force_released_packages.append(package_name) + +previous_packages = set() +for file in os.listdir(previous_packages_path): + if file.endswith(".nupkg"): + previous_packages.add(nuget.get_package_name(file)) + +release_packages = set() +for file in os.listdir(release_packages_path): + if file.endswith(".nupkg"): + release_packages.add(nuget.get_package_name(file)) + +with gha.JobSummary() as md: + def write_both(line: str = ''): + print(line) + md.write_line(line) + + print() + different_packages.sort() + md.write_line("# Packages with changes\n") + if len(different_packages) == 0: + print("There are no packages with any changes.") + md.write_line("*There are no packages with any changes.*") + else: + print("The following packages have changes:") + for package in different_packages: + print(f" {package}") + md.write_line(f"* {package}") + + if len(force_released_packages) > 0: + write_both() + write_both("The following packages are configured to release anyway despite not being changed:") + md.write_line() + force_released_packages.sort() + for package in force_released_packages: + print(f" {package}") + md.write_line(f"* {package}") + + different_packages += force_released_packages + different_packages.sort() + + # Ensure the next dummy reference and release package sets contain the same packages + def list_missing_peers(heading: str, md_heading: str, packages: set[str]) -> bool: + if len(packages) == 0: + return False + + print() + print(heading) + md.write_line(f"# {md_heading}") + md.write_line() + md.write_line(heading) + md.write_line() + for package in packages: + print(f" {package}") + md.write_line(f"* {package}") + return True + + list_missing_peers("The following packages are new for this release:", "New packages", next_packages - previous_packages) + list_missing_peers("The following packages were removed during this release:", "Removed packages", previous_packages - next_packages) + + if list_missing_peers("The following packages exist in the release package artifact, but not in the next dummy reference artifact:", "⚠ Missing reference packages", release_packages - next_packages): + gha.print_error("Some packages exist in the release package artifact, but not in the next dummy reference artifact.") + if list_missing_peers("The following packages exist in the next dummy reference artifact, but not in the release package artifact:", "⚠ Missing release packages", next_packages - release_packages): + gha.print_error("Some packages exist in the next dummy reference artifact, but not in the release package artifact.") + if list_missing_peers("The following packages are marked to always release but do not exist:", "⚠ Missing always-release packages", always_release_packages - release_packages): + gha.print_error("Some packages exist in the always-release list, but not in the release package artifact.") + +with open(release_manifest_path, 'x') as manifest: + for package in different_packages: + manifest.write(f"{package}\n") + +gha.fail_if_errors() diff --git a/.github/workflows/configure-build.py b/.github/workflows/configure-build.py new file mode 100755 index 000000000..b17bbbf45 --- /dev/null +++ b/.github/workflows/configure-build.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +import gha +import nuget + +#================================================================================================== +# Get inputs +#================================================================================================== +def get_environment_variable(name): + ret = os.getenv(name) + + if ret is None: + gha.print_error(f"Missing required parameter '{name}'") + + if ret == '': + return None + + return ret + +github_event_name = get_environment_variable('github_event_name') +assert(github_event_name is not None) +github_ref = get_environment_variable('github_ref') +assert(github_ref is not None) +github_run_number = get_environment_variable('github_run_number') +assert(github_run_number is not None) + +gha.fail_if_errors() + +#================================================================================================== +# Determine build settings +#================================================================================================== + +# For GitHub refs besides main, include the branch/tag name in the default version string +ref_part = '' +if github_ref != 'refs/heads/main': + ref = github_ref + + # Strip the ref prefix + branch_prefix = 'refs/heads/' + tag_prefix = 'refs/tags/' + if ref.startswith(branch_prefix): + ref = ref[len(branch_prefix):] + elif ref.startswith(tag_prefix): + ref = f'tag-{ref[len(tag_prefix):]}' + + # Replace illegal characters with dashes + ref = re.sub('[^0-9A-Za-z-]', '-', ref) + + # Make the ref part + ref_part = f'-{ref}' + +# Build the default version string +version = '' +version_suffix = f'{ref_part}-ci{github_run_number}' +is_for_release = False + +# Handle non-default version strings +# Make sure logic relating to is_for_release matches the publish-packages-nuget-org in the workflow +if github_event_name == 'release': + is_for_release = True + version = get_environment_variable('release_version') + if version is None: + gha.print_error('Release version was not specified!') + sys.exit(1) + + # Trim leading v off of version if present + version = version.strip('v') + + release_is_prerelease = get_environment_variable('release_is_prerelease') + if release_is_prerelease != 'true' and release_is_prerelease != 'false': + gha.print_error('Release prerelease status was invalid or unspecified!') + + # There are steps within the workflow which assume that the prerelease state of the release is correct, so we ensure it is + # We could implicitly detect things for those steps, but this situation probably indicates user error and handling it this way is easier + if nuget.is_preview_version(version) and release_is_prerelease != 'true': + gha.print_error(f"The version to be release '{version}' indicates a pre-release version, but the release is not marked as a pre-release!") + sys.exit(1) +elif github_event_name == 'workflow_dispatch': + workflow_dispatch_version = get_environment_variable('workflow_dispatch_version') + workflow_dispatch_will_publish_packages = get_environment_variable('workflow_dispatch_will_publish_packages') or 'false' + + if workflow_dispatch_version is not None: + version = workflow_dispatch_version + + if workflow_dispatch_will_publish_packages.lower() == 'true': + is_for_release = True + +# Validate the version number +if version != '' and not nuget.is_valid_version(version, forbid_build_metadata=True): + gha.print_error(f"'{version}' is not a valid semver version!") + +# If there are any errors at this point, make sure we exit with an error code +gha.fail_if_errors() + +#================================================================================================== +# Emit MSBuild properties +#================================================================================================== +print(f"Configuring build environment to build{' and release' if is_for_release else ''} version {version}") +gha.set_environment_variable('CiBuildVersion', version) +gha.set_environment_variable('CiBuildVersionSuffix', version_suffix) +gha.set_environment_variable('CiRunNumber', github_run_number) +gha.set_environment_variable('CiIsForRelease', str(is_for_release).lower()) + +gha.fail_if_errors() diff --git a/.github/workflows/create-build-matrix.py b/.github/workflows/create-build-matrix.py new file mode 100755 index 000000000..eacd2b983 --- /dev/null +++ b/.github/workflows/create-build-matrix.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +import json +import os + +import gha + +matrix = [ ] + +def add(name: str, runner_os: str, rid: str, configurations: list[str] = ['Debug', 'Release']): + platform = { + 'name': name, + 'os': runner_os, + 'rid': rid, + } + + ret = { } + for configuration in configurations: + job = { + 'platform': platform.copy(), + 'configuration': configuration, + 'configuration-lower': configuration.lower(), + 'job-title': f"{name} {configuration}", + 'artifacts-suffix': '', + } + matrix.append(job) + ret[configuration] = job + return ret + +windows = add('Windows x64', 'windows-latest', 'win-x64') +linux = add('Linux x64', 'ubuntu-latest', 'linux-x64') + +# Collect packages and create installer from Windows Release x64 +windows['Release']['collect-packages'] = True +windows['Release']['create-installer'] = True + +# Build dummy packages to determine which ones changed (not relevant for pull requests since we won't publish) +def add_dummy(name: str, artifacts_suffix: str): + dummy = add(name, 'ubuntu-latest', 'linux-x64', ['Release'])['Release'] + dummy['skip-tests'] = True + dummy['collect-packages'] = True + dummy['dummy-build'] = True + dummy['title'] = name # Don't include configuration in dummy target titles + dummy['artifacts-suffix'] = artifacts_suffix + return dummy + +if os.getenv('GITHUB_EVENT_NAME') != 'pull_request': + add_dummy('Previous Dummy', '-dummy-prev')['checkout-ref'] = 'refs/tags/latest' + add_dummy('Next Dummy', '-dummy-next') + +# Output +matrix_json = json.dumps({ "include": matrix }, indent=2) +print(matrix_json) +gha.set_output('matrix', matrix_json) + +gha.fail_if_errors() diff --git a/.github/workflows/create-portable-zip.py b/.github/workflows/create-portable-zip.py new file mode 100755 index 000000000..aeea468d0 --- /dev/null +++ b/.github/workflows/create-portable-zip.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +import sys +import zipfile + +import gha + +if len(sys.argv) != 3: + gha.print_error('Usage: create-portable-zip.py ') + sys.exit(1) +else: + output_path = sys.argv[1] + configuration = sys.argv[2].lower() + +with zipfile.ZipFile(output_path, 'x', zipfile.ZIP_DEFLATED, compresslevel=9) as output: + output.mkdir('Extensions') + output.mkdir('Gallery') + + output.write(f'artifacts/bin/Bonsai/{configuration}-repacked/Bonsai.exe', 'Bonsai.exe') + output.write(f'artifacts/bin/Bonsai/{configuration}/Bonsai32.exe', 'Bonsai32.exe') + + nuget_config = [ + '', + '', + ' ', + ' ', + ' ', + ' ', + ] + + nuget_api_url = os.getenv('NUGET_API_URL') + if nuget_api_url is not None: + nuget_config.append(f' ') + + # Unstable builds of Bonsai will automatically reference the GitHub Packages feed + if os.getenv('IS_FULL_RELEASE') == 'false': + repo_owner = os.getenv('GITHUB_REPOSITORY_OWNER') or 'bonsai-rx' + nuget_config.append(f' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + nuget_config.append(' ') + else: + nuget_config.append(' ') + + nuget_config.append('') + nuget_config.append('') + + output.writestr('NuGet.config', '\r\n'.join(nuget_config)) + +gha.fail_if_errors() diff --git a/.github/workflows/filter-release-packages.py b/.github/workflows/filter-release-packages.py new file mode 100755 index 000000000..f67fc5af8 --- /dev/null +++ b/.github/workflows/filter-release-packages.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +import os +import sys + +from pathlib import Path + +import gha +import nuget + +if len(sys.argv) != 3: + gha.print_error('Usage: filter-release-packages.py ') + sys.exit(1) +else: + release_manifest_path = Path(sys.argv[1]) + packages_path = Path(sys.argv[2]) + +if not release_manifest_path.exists(): + gha.print_error(f"Release manifest '{release_manifest_path}' does not exist.") +if not packages_path.exists(): + gha.print_error(f"Packages path '{packages_path}' does not exist.") +gha.fail_if_errors() + +release_packages = set() +with open(release_manifest_path, 'r') as release_manifest: + for line in release_manifest.readlines(): + release_packages.add(line.strip()) + +# The workflow doesn't properly handle this scenario right now since it doesn't have to thanks to some packages being force-released +# In case it happens in the future though, we print an explicit error rather than letting it fail in a confusing way +if len(release_packages) == 0: + gha.print_error("No packages are listed in the release manifest. Everything will be filtered.") + +file_names = os.listdir(packages_path) +file_names.sort() +for file_name in file_names: + extension = Path(file_name).suffix.lower() + if extension != '.nupkg' and extension != '.snupkg': + continue + + package_name = nuget.get_package_name(file_name) + if package_name in release_packages: + if extension != '.snupkg': + print(f"✅ '{package_name}'") + continue + + if extension != '.snupkg': + print(f"⬜ '{package_name}'") + os.unlink(packages_path / file_name) + +gha.fail_if_errors() diff --git a/.github/workflows/gha.py b/.github/workflows/gha.py new file mode 100755 index 000000000..6159d6f3e --- /dev/null +++ b/.github/workflows/gha.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# GitHub Actions Utility Functions +# https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions +import io +import os +import sys + +errors_were_printed = False + +def fail_if_errors(): + if errors_were_printed: + print("Exiting due to previous errors.") + sys.exit(1) + +def print_error(message): + global errors_were_printed + errors_were_printed = True + print(f"::error::{message}") + +def print_warning(message): + print(f"::warning::{message}") + +def print_notice(message): + print(f"::notice::{message}") + +def print_debug(message): + print(f"::debug::{message}") + +def github_file_command(command, message): + command = f"GITHUB_{command}" + command_file = os.getenv(command) + + if command_file is None: + print_error(f"Missing required GitHub environment variable '{command}'") + sys.exit(1) + + if not os.path.exists(command_file): + print_error(f"'{command}' points to non-existent file '{command_file}')") + sys.exit(1) + + with open(command_file, 'a') as command_file_handle: + command_file_handle.write(message) + command_file_handle.write('\n') + +def set_output(name, value): + if isinstance(value, bool): + value = "true" if value else "false" + github_file_command("OUTPUT", f"{name}< None: + if self.file is None: + return + + self.file.write(line) + self.file.write('\n') + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + if self.file is not None: + self.file.__exit__(exc_type, exc_val, exc_tb) + +if __name__ == "__main__": + args = sys.argv + + def pop_arg(): + global args + if len(args) == 0: + print_error("Bad command line, not enough arguments specified.") + sys.exit(1) + result = args[0] + args = args[1:] + return result + + def done_parsing(): + if len(args) > 0: + print_error("Bad command line, too many arguments specified.") + sys.exit(1) + + pop_arg() # Skip script name + command = pop_arg() + if command == "print_error": + message = pop_arg() + done_parsing() + print_error(message) + elif command == "print_warning": + message = pop_arg() + done_parsing() + print_warning(message) + elif command == "print_notice": + message = pop_arg() + done_parsing() + print_notice(message) + elif command == "set_output": + name = pop_arg() + value = pop_arg() + done_parsing() + set_output(name, value) + elif command == "set_environment_variable": + name = pop_arg() + value = pop_arg() + done_parsing() + set_environment_variable(name, value) + elif command == "add_path": + path = pop_arg() + done_parsing() + add_path(path) + else: + print_error(f"Unknown command '{command}'") + sys.exit(1) + + fail_if_errors() diff --git a/.github/workflows/nuget.py b/.github/workflows/nuget.py new file mode 100644 index 000000000..38b42531c --- /dev/null +++ b/.github/workflows/nuget.py @@ -0,0 +1,62 @@ +import re + +import gha + +package_version_regex = re.compile(r"(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?") + +package_file_name_regex = re.compile(r"^(?P.+?)\." + package_version_regex.pattern + r"\.s?nupkg$") +def get_package_name(file_name: str) -> str: + match = package_file_name_regex.match(file_name) + if match is None: + gha.print_warning(f"File name '{file_name}' does not match the expected format for a NuGet package.") + return file_name + return match.group('package_name') + +def is_valid_version(version: str, forbid_build_metadata: bool = False) -> bool: + match = package_version_regex.match(version) + if match is None: + return False + + if forbid_build_metadata and match.group('buildmetadata') is not None: + return False + + return True + +def is_preview_version(version: str) -> bool: + match = package_version_regex.match(version) + if match is None: + gha.print_error(f"Version '{version}' is not a legal semver version string!") + return True + + return match.group('prerelease') is not None + +class SemanticVersion: + major = 0 + minor = 0 + patch = 0 + prerelease: str | None = None + build_metadata: str | None = None + + def __str__(self): + ret = f"{self.major}.{self.minor}.{self.patch}" + + if self.prerelease is not None: + ret += f"-{self.prerelease}" + + if self.build_metadata is not None: + ret += f"+{self.build_metadata}" + + return ret + +def get_version_parts(version: str) -> SemanticVersion: + match = package_version_regex.match(version) + if match is None: + raise Exception("The specified version was invalid") + + ret = SemanticVersion() + ret.major = int(match.group('major')) + ret.minor = int(match.group('minor')) + ret.patch = int(match.group('patch')) + ret.prerelease = match.group('prerelease') + ret.build_metadata = match.group('buildmetadata') + return ret diff --git a/.gitignore b/.gitignore index dac3576e0..bc72129f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -bin -obj -packages +.vs/ +/artifacts/ +/packages/ Resources/*.ico *.Generated.cs -*.orig *.user -*.suo -*.exe -.vs +Bonsai.Setup.Bootstrapper/vc_redist.*.exe diff --git a/Bonsai.Audio/Bonsai.Audio.csproj b/Bonsai.Audio/Bonsai.Audio.csproj index ffef74189..4b9b5e6b6 100644 --- a/Bonsai.Audio/Bonsai.Audio.csproj +++ b/Bonsai.Audio/Bonsai.Audio.csproj @@ -1,11 +1,9 @@ - - + Bonsai - Audio Library Bonsai Audio Library containing modules for sound capture and playback. Bonsai Rx Audio net462 - 2.8.0 diff --git a/Bonsai.Configuration/Bonsai.Configuration.csproj b/Bonsai.Configuration/Bonsai.Configuration.csproj index 9e715778e..fcf3fee07 100644 --- a/Bonsai.Configuration/Bonsai.Configuration.csproj +++ b/Bonsai.Configuration/Bonsai.Configuration.csproj @@ -1,10 +1,8 @@ - - + false false net472;netstandard2.0 - 2.8.3 diff --git a/Bonsai.Core.Tests/Bonsai.Core.Tests.csproj b/Bonsai.Core.Tests/Bonsai.Core.Tests.csproj index 106432754..bdf4b619a 100644 --- a/Bonsai.Core.Tests/Bonsai.Core.Tests.csproj +++ b/Bonsai.Core.Tests/Bonsai.Core.Tests.csproj @@ -3,7 +3,6 @@ false false net462 - 2.8.1 diff --git a/Bonsai.Core/Bonsai.Core.csproj b/Bonsai.Core/Bonsai.Core.csproj index 3b27780ae..8c6d4cd63 100644 --- a/Bonsai.Core/Bonsai.Core.csproj +++ b/Bonsai.Core/Bonsai.Core.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Core Library Bonsai Core Library containing base classes and workflow infrastructure. Bonsai Rx Reactive Extensions net462;netstandard2.0;net6.0 Bonsai - 2.8.2 diff --git a/Bonsai.Design.Visualizers/Bonsai.Design.Visualizers.csproj b/Bonsai.Design.Visualizers/Bonsai.Design.Visualizers.csproj index 3f4750277..c55e37a4b 100644 --- a/Bonsai.Design.Visualizers/Bonsai.Design.Visualizers.csproj +++ b/Bonsai.Design.Visualizers/Bonsai.Design.Visualizers.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Visualizers Library Bonsai Visualizers Library containing base visualizer classes and editor infrastructure. Bonsai Rx Visualizers true net462 - 2.8.0 diff --git a/Bonsai.Design/Bonsai.Design.csproj b/Bonsai.Design/Bonsai.Design.csproj index 096308144..d5a5faa42 100644 --- a/Bonsai.Design/Bonsai.Design.csproj +++ b/Bonsai.Design/Bonsai.Design.csproj @@ -1,15 +1,14 @@ - - + Bonsai - Design Library Bonsai Design Library containing base visualizer classes and editor infrastructure. Bonsai Design Rx Reactive Extensions true net462 - 2.8.1 + diff --git a/Bonsai.Dsp.Design/Bonsai.Dsp.Design.csproj b/Bonsai.Dsp.Design/Bonsai.Dsp.Design.csproj index 33e128654..e8224c854 100644 --- a/Bonsai.Dsp.Design/Bonsai.Dsp.Design.csproj +++ b/Bonsai.Dsp.Design/Bonsai.Dsp.Design.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Dsp Design Library Bonsai Design Library containing visualizer and editor classes for signal processing data types. Bonsai Rx Dsp Visualizers true net462 - 2.8.0 diff --git a/Bonsai.Dsp/Bonsai.Dsp.csproj b/Bonsai.Dsp/Bonsai.Dsp.csproj index 7c45008e9..d2dd76534 100644 --- a/Bonsai.Dsp/Bonsai.Dsp.csproj +++ b/Bonsai.Dsp/Bonsai.Dsp.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Dsp Library Bonsai Dsp Library containing reactive algorithms for digital signal processing. Bonsai Rx Dsp Signal Processing true net462 - 2.8.1 diff --git a/Bonsai.Editor.Tests/Bonsai.Editor.Tests.csproj b/Bonsai.Editor.Tests/Bonsai.Editor.Tests.csproj index fa261f0cb..1f6d31481 100644 --- a/Bonsai.Editor.Tests/Bonsai.Editor.Tests.csproj +++ b/Bonsai.Editor.Tests/Bonsai.Editor.Tests.csproj @@ -3,7 +3,6 @@ false false net472 - 2.8.2 diff --git a/Bonsai.Editor/AboutBox.Designer.cs b/Bonsai.Editor/AboutBox.Designer.cs index 0ff1b932c..2efe4e942 100644 --- a/Bonsai.Editor/AboutBox.Designer.cs +++ b/Bonsai.Editor/AboutBox.Designer.cs @@ -27,6 +27,7 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { + this.okButton = new System.Windows.Forms.Button(); this.tableLayoutPanel = new Bonsai.Editor.TableLayoutPanel(); this.logoPictureBox = new System.Windows.Forms.PictureBox(); this.labelProductName = new Bonsai.Editor.Label(); @@ -34,16 +35,25 @@ private void InitializeComponent() this.labelCopyright = new Bonsai.Editor.Label(); this.labelCompanyName = new Bonsai.Editor.Label(); this.textBoxDescription = new System.Windows.Forms.RichTextBox(); - this.okButton = new System.Windows.Forms.Button(); this.tableLayoutPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).BeginInit(); this.SuspendLayout(); // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.okButton.Location = new System.Drawing.Point(387, 239); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 24; + this.okButton.Text = "&OK"; + // // tableLayoutPanel // this.tableLayoutPanel.ColumnCount = 2; - this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33F)); - this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 67F)); + this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 31.6129F)); + this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 68.3871F)); this.tableLayoutPanel.Controls.Add(this.logoPictureBox, 0, 4); this.tableLayoutPanel.Controls.Add(this.labelProductName, 1, 0); this.tableLayoutPanel.Controls.Add(this.labelVersion, 1, 1); @@ -61,7 +71,7 @@ private void InitializeComponent() this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 10F)); - this.tableLayoutPanel.Size = new System.Drawing.Size(417, 265); + this.tableLayoutPanel.Size = new System.Drawing.Size(465, 265); this.tableLayoutPanel.TabIndex = 0; // // logoPictureBox @@ -70,55 +80,59 @@ private void InitializeComponent() this.logoPictureBox.Image = global::Bonsai.Editor.Properties.Resources.Bonsai; this.logoPictureBox.Location = new System.Drawing.Point(3, 107); this.logoPictureBox.Name = "logoPictureBox"; - this.logoPictureBox.Size = new System.Drawing.Size(131, 126); + this.logoPictureBox.Size = new System.Drawing.Size(141, 126); this.logoPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.logoPictureBox.TabIndex = 12; this.logoPictureBox.TabStop = false; // // labelProductName // + this.labelProductName.AutoEllipsis = true; this.labelProductName.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelProductName.Location = new System.Drawing.Point(143, 0); + this.labelProductName.Location = new System.Drawing.Point(153, 0); this.labelProductName.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); this.labelProductName.MaximumSize = new System.Drawing.Size(0, 17); this.labelProductName.Name = "labelProductName"; - this.labelProductName.Size = new System.Drawing.Size(271, 17); + this.labelProductName.Size = new System.Drawing.Size(309, 17); this.labelProductName.TabIndex = 19; this.labelProductName.Text = "Product Name"; this.labelProductName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // labelVersion // + this.labelVersion.AutoEllipsis = true; this.labelVersion.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelVersion.Location = new System.Drawing.Point(143, 26); + this.labelVersion.Location = new System.Drawing.Point(153, 26); this.labelVersion.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); this.labelVersion.MaximumSize = new System.Drawing.Size(0, 17); this.labelVersion.Name = "labelVersion"; - this.labelVersion.Size = new System.Drawing.Size(271, 17); + this.labelVersion.Size = new System.Drawing.Size(309, 17); this.labelVersion.TabIndex = 0; this.labelVersion.Text = "Version"; this.labelVersion.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // labelCopyright // + this.labelCopyright.AutoEllipsis = true; this.labelCopyright.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelCopyright.Location = new System.Drawing.Point(143, 52); + this.labelCopyright.Location = new System.Drawing.Point(153, 52); this.labelCopyright.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); this.labelCopyright.MaximumSize = new System.Drawing.Size(0, 17); this.labelCopyright.Name = "labelCopyright"; - this.labelCopyright.Size = new System.Drawing.Size(271, 17); + this.labelCopyright.Size = new System.Drawing.Size(309, 17); this.labelCopyright.TabIndex = 21; this.labelCopyright.Text = "Copyright"; this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // labelCompanyName // + this.labelCompanyName.AutoEllipsis = true; this.labelCompanyName.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelCompanyName.Location = new System.Drawing.Point(143, 78); + this.labelCompanyName.Location = new System.Drawing.Point(153, 78); this.labelCompanyName.Margin = new System.Windows.Forms.Padding(6, 0, 3, 0); this.labelCompanyName.MaximumSize = new System.Drawing.Size(0, 17); this.labelCompanyName.Name = "labelCompanyName"; - this.labelCompanyName.Size = new System.Drawing.Size(271, 17); + this.labelCompanyName.Size = new System.Drawing.Size(309, 17); this.labelCompanyName.TabIndex = 22; this.labelCompanyName.Text = "Company Name"; this.labelCompanyName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; @@ -126,33 +140,21 @@ private void InitializeComponent() // textBoxDescription // this.textBoxDescription.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxDescription.Location = new System.Drawing.Point(143, 107); + this.textBoxDescription.Location = new System.Drawing.Point(153, 107); this.textBoxDescription.Margin = new System.Windows.Forms.Padding(6, 3, 3, 3); - this.textBoxDescription.Multiline = true; this.textBoxDescription.Name = "textBoxDescription"; this.textBoxDescription.ReadOnly = true; - this.textBoxDescription.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.Both; - this.textBoxDescription.Size = new System.Drawing.Size(271, 126); + this.textBoxDescription.Size = new System.Drawing.Size(309, 126); this.textBoxDescription.TabIndex = 23; this.textBoxDescription.TabStop = false; this.textBoxDescription.Text = "Description"; // - // okButton - // - this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.okButton.Location = new System.Drawing.Point(339, 239); - this.okButton.Name = "okButton"; - this.okButton.Size = new System.Drawing.Size(75, 23); - this.okButton.TabIndex = 24; - this.okButton.Text = "&OK"; - // // AboutBox // this.AcceptButton = this.okButton; this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(435, 283); + this.ClientSize = new System.Drawing.Size(483, 283); this.Controls.Add(this.tableLayoutPanel); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; this.MaximizeBox = false; @@ -164,7 +166,6 @@ private void InitializeComponent() this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "AboutBox"; this.tableLayoutPanel.ResumeLayout(false); - this.tableLayoutPanel.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.logoPictureBox)).EndInit(); this.ResumeLayout(false); diff --git a/Bonsai.Editor/AboutBox.cs b/Bonsai.Editor/AboutBox.cs index e01f4db20..442d3ee71 100644 --- a/Bonsai.Editor/AboutBox.cs +++ b/Bonsai.Editor/AboutBox.cs @@ -11,14 +11,32 @@ partial class AboutBox : Form public AboutBox() { InitializeComponent(); - this.Text = String.Format("About {0}", AssemblyTitle); + this.Text = $"About {AssemblyTitle}{BuildKindTitleSuffix}"; this.labelProductName.Text = AssemblyProduct; - this.labelVersion.Text = String.Format("Version {0}", AssemblyVersion); + this.labelVersion.Text = $"Version {AssemblyVersion}"; this.labelCopyright.Text = AssemblyCopyright; this.labelCompanyName.Text = AssemblyCompany; this.textBoxDescription.Text = AssemblyDescription + Environment.NewLine + Resources.AttributionNotices; } + internal static string BuildKindTitleSuffix + { + get + { +#if BUILD_KIND_DEV + return " [Dev]"; +#elif BUILD_KIND_UNSTABLE + return " [Unstable]"; +#elif BUILD_KIND_PREVIEW + return " [Preview]"; +#elif BUILD_KIND_OFFICIAL_RELEASE + return ""; +#else +#error "Unknown build kind!" +#endif + } + } + #region Assembly Attribute Accessors public string AssemblyTitle diff --git a/Bonsai.Editor/Bonsai.Editor.csproj b/Bonsai.Editor/Bonsai.Editor.csproj index 59b16a9d0..f93badbb0 100644 --- a/Bonsai.Editor/Bonsai.Editor.csproj +++ b/Bonsai.Editor/Bonsai.Editor.csproj @@ -1,5 +1,4 @@ - - + Bonsai - Editor An integrated development environment for the Bonsai visual programming language. @@ -7,7 +6,6 @@ true false net472 - 2.8.2 diff --git a/Bonsai.Editor/EditorForm.cs b/Bonsai.Editor/EditorForm.cs index bf12a9e4d..e90ad05f9 100644 --- a/Bonsai.Editor/EditorForm.cs +++ b/Bonsai.Editor/EditorForm.cs @@ -187,6 +187,8 @@ public EditorForm( editorControl.Leave += delegate { menuStrip.Enabled = true; }; } components.Add(editorControl); + + UpdateTitle(); } #region Loading @@ -1473,6 +1475,7 @@ void UpdateTitle() if (modified) title.Append('*'); if (workflowRunning) title.AppendFormat(" ({0})", Resources.RunningStatus); if (!emptyFileName) title.AppendFormat(" - {0}", Resources.BonsaiTitle); + title.Append(AboutBox.BuildKindTitleSuffix); Text = title.ToString(); } diff --git a/Bonsai.Editor/StartScreen.cs b/Bonsai.Editor/StartScreen.cs index 3fe2a5582..07cbefc38 100644 --- a/Bonsai.Editor/StartScreen.cs +++ b/Bonsai.Editor/StartScreen.cs @@ -36,6 +36,7 @@ public StartScreen() openTreeView.Nodes.Add(galleryNode); openTreeView.Nodes.Add(packageManagerNode); FileName = string.Empty; + Text += AboutBox.BuildKindTitleSuffix; } public EditorResult EditorResult { get; private set; } diff --git a/Bonsai.NuGet.Design/Bonsai.NuGet.Design.csproj b/Bonsai.NuGet.Design/Bonsai.NuGet.Design.csproj index f27b5385b..7c7b29f51 100644 --- a/Bonsai.NuGet.Design/Bonsai.NuGet.Design.csproj +++ b/Bonsai.NuGet.Design/Bonsai.NuGet.Design.csproj @@ -1,11 +1,9 @@ - - + true false false net472 - 2.8.1 diff --git a/Bonsai.NuGet/Bonsai.NuGet.csproj b/Bonsai.NuGet/Bonsai.NuGet.csproj index acf9277de..6aea615d1 100644 --- a/Bonsai.NuGet/Bonsai.NuGet.csproj +++ b/Bonsai.NuGet/Bonsai.NuGet.csproj @@ -1,10 +1,8 @@ - - + false false net472;netstandard2.0 - 2.8.1 diff --git a/Bonsai.Osc/Bonsai.Osc.csproj b/Bonsai.Osc/Bonsai.Osc.csproj index bf35c2c00..d23e48dbd 100644 --- a/Bonsai.Osc/Bonsai.Osc.csproj +++ b/Bonsai.Osc/Bonsai.Osc.csproj @@ -1,11 +1,9 @@ - - + Bonsai - Osc Library Bonsai Osc Library containing reactive infrastructure to interface with devices using the Open Sound Control protocol. Bonsai Rx Osc net462 - 2.7.0 diff --git a/Bonsai.Player/Bonsai.Player.csproj b/Bonsai.Player/Bonsai.Player.csproj index 72c21db13..7ce2e0d48 100644 --- a/Bonsai.Player/Bonsai.Player.csproj +++ b/Bonsai.Player/Bonsai.Player.csproj @@ -1,5 +1,4 @@ - - + Exe Bonsai - Player @@ -12,7 +11,6 @@ icon.png - 2.8.3 true diff --git a/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj b/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj index 44b996489..5618a2a91 100644 --- a/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj +++ b/Bonsai.Scripting.Expressions.Design/Bonsai.Scripting.Expressions.Design.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Expression Scripting Design Library Bonsai Design Library containing editor classes for expression scripting in Bonsai. Bonsai Rx Scripting Expressions Design true net462 - 2.8.0 diff --git a/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj b/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj index 4a7a27d5d..f0838fad0 100644 --- a/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj +++ b/Bonsai.Scripting.Expressions/Bonsai.Scripting.Expressions.csproj @@ -1,11 +1,9 @@ - - + Bonsai - Expression Scripting Library Bonsai Scripting Library containing expression scripting infrastructure for Bonsai. Bonsai Rx Scripting Expressions net462 - 2.8.0 diff --git a/Bonsai.Scripting.IronPython.Design/Bonsai.Scripting.IronPython.Design.csproj b/Bonsai.Scripting.IronPython.Design/Bonsai.Scripting.IronPython.Design.csproj index 1afe3e763..b34eff060 100644 --- a/Bonsai.Scripting.IronPython.Design/Bonsai.Scripting.IronPython.Design.csproj +++ b/Bonsai.Scripting.IronPython.Design/Bonsai.Scripting.IronPython.Design.csproj @@ -1,12 +1,10 @@ - - + Bonsai - IronPython Scripting Design Library Bonsai Design Library containing editor classes for IronPython scripting in Bonsai. Bonsai Rx Scripting Iron Python Design true net462 - 2.8.0 diff --git a/Bonsai.Scripting.IronPython/Bonsai.Scripting.IronPython.csproj b/Bonsai.Scripting.IronPython/Bonsai.Scripting.IronPython.csproj index f039e497a..033c59202 100644 --- a/Bonsai.Scripting.IronPython/Bonsai.Scripting.IronPython.csproj +++ b/Bonsai.Scripting.IronPython/Bonsai.Scripting.IronPython.csproj @@ -1,11 +1,9 @@ - - + Bonsai - IronPython Scripting Library Bonsai Scripting Library containing IronPython scripting infrastructure for Bonsai. Bonsai Rx Scripting Iron Python net462 - 2.8.0 diff --git a/Bonsai.Scripting/Bonsai.Scripting.csproj b/Bonsai.Scripting/Bonsai.Scripting.csproj index 61d7d1641..3bfcb3edb 100644 --- a/Bonsai.Scripting/Bonsai.Scripting.csproj +++ b/Bonsai.Scripting/Bonsai.Scripting.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Scripting Library Bonsai Scripting Library containing scripting infrastructure for Bonsai. Bonsai Rx Scripting true net462 - 2.8.0 diff --git a/Bonsai.Setup.Bootstrapper/Bonsai.Setup.Bootstrapper.wixproj b/Bonsai.Setup.Bootstrapper/Bonsai.Setup.Bootstrapper.wixproj index fe4daa6e7..12c576808 100644 --- a/Bonsai.Setup.Bootstrapper/Bonsai.Setup.Bootstrapper.wixproj +++ b/Bonsai.Setup.Bootstrapper/Bonsai.Setup.Bootstrapper.wixproj @@ -1,5 +1,6 @@  + Debug @@ -7,30 +8,14 @@ 3.6 {da1c1aa7-15f6-4787-b441-e75bcffe680b} 2.0 - Bonsai-2.8.3 + Bonsai-$(Version) Bundle $(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - Debug - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - Debug - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ + Debug + $(DefineConstants);BonsaiDisplayVersion=$(Version) @@ -77,12 +62,5 @@ - + \ No newline at end of file diff --git a/Bonsai.Setup.Bootstrapper/Bundle.wxs b/Bonsai.Setup.Bootstrapper/Bundle.wxs index cfd5f39f1..038a1a546 100644 --- a/Bonsai.Setup.Bootstrapper/Bundle.wxs +++ b/Bonsai.Setup.Bootstrapper/Bundle.wxs @@ -22,6 +22,7 @@ LaunchWorkingFolder="[InstallFolder]"/> + diff --git a/Bonsai.Setup.Bootstrapper/Theme/RtfLargeTheme.xml b/Bonsai.Setup.Bootstrapper/Theme/RtfLargeTheme.xml index 0fefb0221..bcece6ca3 100644 --- a/Bonsai.Setup.Bootstrapper/Theme/RtfLargeTheme.xml +++ b/Bonsai.Setup.Bootstrapper/Theme/RtfLargeTheme.xml @@ -18,7 +18,7 @@ - #(loc.InstallVersion) + Version [BonsaiDisplayVersion] #(loc.InstallAcceptCheckbox) diff --git a/Bonsai.Setup/Bonsai.Setup.wixproj b/Bonsai.Setup/Bonsai.Setup.wixproj index 87e69ff6e..7dd3eea82 100644 --- a/Bonsai.Setup/Bonsai.Setup.wixproj +++ b/Bonsai.Setup/Bonsai.Setup.wixproj @@ -1,5 +1,6 @@  + Debug @@ -13,24 +14,7 @@ $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - Debug - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ - Debug - - - bin\$(Platform)\$(Configuration)\ - obj\$(Platform)\$(Configuration)\ + Debug @@ -63,12 +47,5 @@ - + \ No newline at end of file diff --git a/Bonsai.Setup/Development.wxs b/Bonsai.Setup/Development.wxs index fc46c0524..7a68fd5f2 100644 --- a/Bonsai.Setup/Development.wxs +++ b/Bonsai.Setup/Development.wxs @@ -2,7 +2,7 @@ - + diff --git a/Bonsai.Setup/Runtime.wxs b/Bonsai.Setup/Runtime.wxs index 57b6c9d99..71b0271a1 100644 --- a/Bonsai.Setup/Runtime.wxs +++ b/Bonsai.Setup/Runtime.wxs @@ -4,7 +4,8 @@ - + + @@ -21,7 +22,7 @@ INSTALL64 - + diff --git a/Bonsai.Shaders.Design/Bonsai.Shaders.Design.csproj b/Bonsai.Shaders.Design/Bonsai.Shaders.Design.csproj index bcc328ae6..708133894 100644 --- a/Bonsai.Shaders.Design/Bonsai.Shaders.Design.csproj +++ b/Bonsai.Shaders.Design/Bonsai.Shaders.Design.csproj @@ -1,12 +1,10 @@ - - + Bonsai - Shaders Design Library Bonsai Design Library containing editor classes for shader configuration. Bonsai Rx Shaders Design Graphics OpenGL true net462 - 0.27.0 diff --git a/Bonsai.Shaders.Rendering/Bonsai.Shaders.Rendering.csproj b/Bonsai.Shaders.Rendering/Bonsai.Shaders.Rendering.csproj index 63d7d530b..b980d5f6e 100644 --- a/Bonsai.Shaders.Rendering/Bonsai.Shaders.Rendering.csproj +++ b/Bonsai.Shaders.Rendering/Bonsai.Shaders.Rendering.csproj @@ -1,11 +1,9 @@ - - + Bonsai - Shaders Rendering Library Bonsai Shaders Rendering Library containing modules for rendering complex 3D scenes. Bonsai Rx Shaders Rendering Assets Graphics OpenGL net462 - 0.3.1 diff --git a/Bonsai.Shaders/Bonsai.Shaders.csproj b/Bonsai.Shaders/Bonsai.Shaders.csproj index 245c0b99c..af7d43676 100644 --- a/Bonsai.Shaders/Bonsai.Shaders.csproj +++ b/Bonsai.Shaders/Bonsai.Shaders.csproj @@ -1,11 +1,9 @@ - - + Bonsai - Shaders Library Bonsai Shaders Library containing modules for dynamic control of shader primitives. Bonsai Rx Shaders Graphics OpenGL net462 - 0.27.1 diff --git a/Bonsai.StarterPack/Bonsai.StarterPack.csproj b/Bonsai.StarterPack/Bonsai.StarterPack.csproj index 4ddf1bf52..e0274efc8 100644 --- a/Bonsai.StarterPack/Bonsai.StarterPack.csproj +++ b/Bonsai.StarterPack/Bonsai.StarterPack.csproj @@ -1,13 +1,18 @@ - - + Bonsai - Starter Pack The Bonsai Starter Pack includes everything you need to build your own data processing workflows. Bonsai Rx Starter Pack false false + false net462 - 2.8.1 + + + $(NoWarn);NU5128 diff --git a/Bonsai.System.Design/Bonsai.System.Design.csproj b/Bonsai.System.Design/Bonsai.System.Design.csproj index 8050f371b..37f43e53a 100644 --- a/Bonsai.System.Design/Bonsai.System.Design.csproj +++ b/Bonsai.System.Design/Bonsai.System.Design.csproj @@ -1,12 +1,10 @@ - - + Bonsai - System Design Library Bonsai Design Library containing editor classes for IO and other system configurations. Bonsai Rx Reactive Extensions IO Resources Design true net462 - 2.8.0 diff --git a/Bonsai.System.Tests/Bonsai.System.Tests.csproj b/Bonsai.System.Tests/Bonsai.System.Tests.csproj index 4be4def9b..35ec7273b 100644 --- a/Bonsai.System.Tests/Bonsai.System.Tests.csproj +++ b/Bonsai.System.Tests/Bonsai.System.Tests.csproj @@ -3,7 +3,6 @@ false false net462 - 2.8.0 diff --git a/Bonsai.System/Bonsai.System.csproj b/Bonsai.System/Bonsai.System.csproj index 3df586c3e..5c37abf2c 100644 --- a/Bonsai.System/Bonsai.System.csproj +++ b/Bonsai.System/Bonsai.System.csproj @@ -1,11 +1,9 @@ - - + Bonsai - System Library Bonsai System Library containing reactive infrastructure to interface with the underlying operating system. Bonsai Rx Reactive Extensions IO Serial Port Resources net462;netstandard2.0 - 2.8.1 diff --git a/Bonsai.Templates/Bonsai.PackageTemplate/Bonsai.PackageTemplate.csproj b/Bonsai.Templates/Bonsai.PackageTemplate/Bonsai.PackageTemplate.csproj index 0e642e4d9..0bf3de9f3 100644 --- a/Bonsai.Templates/Bonsai.PackageTemplate/Bonsai.PackageTemplate.csproj +++ b/Bonsai.Templates/Bonsai.PackageTemplate/Bonsai.PackageTemplate.csproj @@ -47,9 +47,6 @@ 4 - - False - diff --git a/Bonsai.Templates/Bonsai.SinkTemplate/Bonsai.SinkTemplate.csproj b/Bonsai.Templates/Bonsai.SinkTemplate/Bonsai.SinkTemplate.csproj index 474ecf33c..662a21d3f 100644 --- a/Bonsai.Templates/Bonsai.SinkTemplate/Bonsai.SinkTemplate.csproj +++ b/Bonsai.Templates/Bonsai.SinkTemplate/Bonsai.SinkTemplate.csproj @@ -45,9 +45,6 @@ 4 - - False - diff --git a/Bonsai.Templates/Bonsai.SourceTemplate/Bonsai.SourceTemplate.csproj b/Bonsai.Templates/Bonsai.SourceTemplate/Bonsai.SourceTemplate.csproj index c63deb6e3..9dfca43cb 100644 --- a/Bonsai.Templates/Bonsai.SourceTemplate/Bonsai.SourceTemplate.csproj +++ b/Bonsai.Templates/Bonsai.SourceTemplate/Bonsai.SourceTemplate.csproj @@ -45,9 +45,6 @@ 4 - - False - diff --git a/Bonsai.Templates/Bonsai.Templates.csproj b/Bonsai.Templates/Bonsai.Templates.csproj index 2c99f72ee..f19667428 100644 --- a/Bonsai.Templates/Bonsai.Templates.csproj +++ b/Bonsai.Templates/Bonsai.Templates.csproj @@ -2,7 +2,6 @@ Template - 2.8.2 Bonsai.Templates Bonsai Templates Bonsai @@ -10,12 +9,24 @@ Bonsai Rx Package Environment Templates netstandard2.0 true - false content LICENSE icon.png + + + false + false + + + $(NoWarn);NU5110;NU5111 + + + $(NoWarn);NU5128 diff --git a/Bonsai.Templates/Bonsai.Templates/Bonsai.Templates.csproj b/Bonsai.Templates/Bonsai.Templates/Bonsai.Templates.csproj index 3f99cc4c3..db8d69f99 100644 --- a/Bonsai.Templates/Bonsai.Templates/Bonsai.Templates.csproj +++ b/Bonsai.Templates/Bonsai.Templates/Bonsai.Templates.csproj @@ -103,6 +103,9 @@ TemplateProjectOutputGroup%3b + + + + + + + + + + + + + + + + + + + + + + + + $(BaseOutputPath)$(ArtifactsPivots)-repacked\ + $(RepackedOutputPath)$(TargetFileName) + $([System.IO.Path]::ChangeExtension('$(RepackedOutputFilePath)', '.pdb')) + + + + + + + + + + - $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework) + <_IlRepackCommand> +"$(ILRepack)" +/verbose +/out:"$(RepackedOutputFilePath)" +/internalize:"$(MSBuildThisFileDirectory)InternalizeExcludePatterns" +"$(OutputPath)Bonsai.exe" +"$(OutputPath)System.Collections.Immutable.dll" +"$(OutputPath)System.Reflection.Metadata.dll" +"$(OutputPath)System.Reflection.MetadataLoadContext.dll" +"$(OutputPath)Bonsai.Configuration.dll" +"$(OutputPath)Bonsai.NuGet.dll" +"$(OutputPath)Bonsai.NuGet.Design.dll" +"$(OutputPath)Newtonsoft.Json.dll" +"$(OutputPath)NuGet.Common.dll" +"$(OutputPath)NuGet.Configuration.dll" +"$(OutputPath)NuGet.Frameworks.dll" +"$(OutputPath)NuGet.Packaging.dll" +"$(OutputPath)NuGet.Protocol.dll" +"$(OutputPath)NuGet.Resolver.dll" +"$(OutputPath)NuGet.Versioning.dll" + + + + + + - - - - - - - - - - - - - - - - - - + + + + + - + + - - - - \ No newline at end of file diff --git a/Bonsai/InternalizeExcludePatterns b/Bonsai/InternalizeExcludePatterns new file mode 100644 index 000000000..19e1cbb2f --- /dev/null +++ b/Bonsai/InternalizeExcludePatterns @@ -0,0 +1 @@ +^Bonsai\.Configuration \ No newline at end of file diff --git a/Bonsai/Program.cs b/Bonsai/Program.cs index e77fb2f64..43fc19e31 100644 --- a/Bonsai/Program.cs +++ b/Bonsai/Program.cs @@ -41,6 +41,8 @@ static class Program [LoaderOptimization(LoaderOptimization.MultiDomainHost)] internal static int Main(string[] args) { + SystemResourcesExtensionsSupport.Initialize(); + var start = false; var bootstrap = true; var debugging = false; @@ -170,7 +172,11 @@ internal static int Main(string[] args) { var bootstrapper = launchEditor ? (Bootstrapper)new EditorBootstrapper(editorRepositoryPath) : new ConsoleBootstrapper(editorRepositoryPath); try { bootstrapper.RunAsync(Launcher.ProjectFramework, packageConfiguration, editorPath, editorPackageName).Wait(); } - catch (AggregateException) { return ErrorExitCode; } + catch (AggregateException ex) + { + Console.Error.WriteLine(ex); + return ErrorExitCode; + } var startScreen = launchEditor; var pipeName = Guid.NewGuid().ToString(); diff --git a/Bonsai/SystemResourcesExtensionsSupport.cs b/Bonsai/SystemResourcesExtensionsSupport.cs new file mode 100644 index 000000000..a0c46421f --- /dev/null +++ b/Bonsai/SystemResourcesExtensionsSupport.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics; +using System.Reflection; + +namespace Bonsai +{ + /// Enables support for System.Resources.Extensions when the bootstrapper is ILRepacked + /// + /// System.Resources.Extensions cannot be cleanly repacked because resources using modern resource embedding internally refer to it + /// using an explicit strong assembly name. As such the runtime will refuse to consider our own assembly a valid provider for it. + /// + /// In order to work around this, we embed it (and its dependencies) directly and load them in an assembly resolver instead. + /// + internal static class SystemResourcesExtensionsSupport + { + [Conditional("NETFRAMEWORK")] + internal static void Initialize() + { + AppDomain.CurrentDomain.AssemblyResolve += (_, args) => + { + var assemblyName = new AssemblyName(args.Name); + using var embeddedAssembly = typeof(SystemResourcesExtensionsSupport).Assembly.GetManifestResourceStream($"{nameof(Bonsai)}.{assemblyName.Name}.dll"); + + if (embeddedAssembly is null) + return null; + + var assemblyBytes = new byte[embeddedAssembly.Length]; + int readLength = embeddedAssembly.Read(assemblyBytes, 0, assemblyBytes.Length); + Debug.Assert(readLength == assemblyBytes.Length); + + var result = Assembly.Load(assemblyBytes); + Debug.WriteLine($"Redirecting '{args.Name}' to embedded '{result.FullName}'"); + return result; + }; + } + } +} diff --git a/Bonsai32/Bonsai32.csproj b/Bonsai32/Bonsai32.csproj index dcd85827a..5ca078e81 100644 --- a/Bonsai32/Bonsai32.csproj +++ b/Bonsai32/Bonsai32.csproj @@ -1,21 +1,34 @@ - - + Bonsai32 The x86 bootstrapper for the Bonsai environment. Bonsai Rx Reactive Extensions true false - false + false net48 x86 - 2.8.3 Exe - ..\Bonsai\bin\$(Configuration)\ + $(ArtifactsPath)\bin\Bonsai\$(Configuration.ToLowerInvariant())\ ..\Bonsai.Editor\Bonsai.ico App.manifest + + + false + true - + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index caa3e5688..c02a48a53 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,20 +1,4 @@ - - - - Bonsai Foundation - Copyright © Bonsai Foundation CIC and Contributors 2011-2024 - https://bonsai-rx.org - https://bonsai-rx.org/license - https://bonsai-rx.org/assets/images/bonsai.png - ..\bin\$(Configuration) - true - true - true - snupkg - https://github.com/bonsai-rx/bonsai.git - git - - 9.0 - strict - + + + \ No newline at end of file diff --git a/Directory.Build.rsp b/Directory.Build.rsp new file mode 100644 index 000000000..17ab7c1dc --- /dev/null +++ b/Directory.Build.rsp @@ -0,0 +1 @@ +# This file intentionally left blank. \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets index c62d5f14f..a7826fdc9 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,7 +1,4 @@ - - - - $([System.DateTime]::UtcNow.Subtract($([System.DateTime]::Parse(2011/12/12))).Days) - $(VersionPrefix).$(DaysFromFirstRevision) - + + + \ No newline at end of file diff --git a/README.md b/README.md index 583b0515f..89787ce16 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Bonsai - Visual Reactive Programming +[![Download Bonsai](https://img.shields.io/nuget/v/Bonsai?style=flat-square&label=Download%20Bonsai&labelColor=5e616c&color=ec2227)](https://bonsai-rx.org/docs/articles/installation.html) +[![CI Status](https://img.shields.io/github/actions/workflow/status/bonsai-rx/bonsai/Bonsai.yml?branch=main&style=flat-square&label=CI)](https://github.com/bonsai-rx/bonsai/actions) + This is the main repository for the [Bonsai](https://bonsai-rx.org/) visual programming language. It contains source code for the compiler, IDE, and standard library. With Bonsai you tell your computer what to do not through long listings of text but by manipulating graphical elements in a workflow. Bonsai is built on top of [Rx.NET](http://reactivex.io/), and like in Rx, workflow elements in Bonsai represent asynchronous streams of data called [Observables](https://bonsai-rx.org/docs/articles/observables.html) which can be connected together to perform complex operations. diff --git a/global.json b/global.json new file mode 100644 index 000000000..989a69caf --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestMinor" + } +} \ No newline at end of file diff --git a/tooling/Common.csproj.props b/tooling/Common.csproj.props new file mode 100644 index 000000000..fd39c36d8 --- /dev/null +++ b/tooling/Common.csproj.props @@ -0,0 +1,39 @@ + + + + 9.0 + strict + true + + + Bonsai Foundation + Copyright © Bonsai Foundation CIC and Contributors 2011-2024 + https://bonsai-rx.org + https://bonsai-rx.org/license + https://bonsai-rx.org/assets/images/bonsai.png + true + + + true + + + true + false + snupkg + + + $(NoWarn);NU5125;NU5048 + + + false + true + + + true + + + none + + + \ No newline at end of file diff --git a/tooling/Common.props b/tooling/Common.props new file mode 100644 index 000000000..c9678bf9e --- /dev/null +++ b/tooling/Common.props @@ -0,0 +1,37 @@ + + + + Debug + AnyCPU + + + + + true + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../artifacts')) + + + $(ArtifactsPath)/wsl + + + + + <_ThisProjectOutputSubdirectory>$(MSBuildProjectName) + + + <_ThisProjectOutputSubdirectory Condition="'$(_ThisProjectOutputSubdirectory)' == 'Bonsai.Templates'">$(MSBuildProjectName)-vsix + + $(ArtifactsPath)/bin/ + $(BaseOutputPath)$(_ThisProjectOutputSubdirectory)/$(Configuration.ToLowerInvariant())-$(Platform.ToLowerInvariant())/ + $(OutputPath) + $(OutputPath) + + $(ArtifactsPath)/obj/$(_ThisProjectOutputSubdirectory)/ + $(BaseIntermediateOutputPath)$(Configuration.ToLowerInvariant())-$(Platform.ToLowerInvariant())/ + + $(ArtifactsPath)/package/$(Configuration.ToLowerInvariant()) + + \ No newline at end of file diff --git a/tooling/Common.targets b/tooling/Common.targets new file mode 100644 index 000000000..707b0e2b6 --- /dev/null +++ b/tooling/Common.targets @@ -0,0 +1,24 @@ + + + + <_RelativeProjectPathFilePath>$(BaseIntermediateOutputPath)RelativeProjectPath.txt + <_ProjectPathRelativeToIntermediateDirectory>$([MSBuild]::MakeRelative($(BaseIntermediateOutputPath), $(MSBuildProjectFullPath))) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tooling/Common.wixproj.props b/tooling/Common.wixproj.props new file mode 100644 index 000000000..53ed6caec --- /dev/null +++ b/tooling/Common.wixproj.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tooling/CurrentVersion.props b/tooling/CurrentVersion.props new file mode 100644 index 000000000..6193f263e --- /dev/null +++ b/tooling/CurrentVersion.props @@ -0,0 +1,6 @@ + + + + 2.9.0 + + \ No newline at end of file diff --git a/tooling/Versioning.props b/tooling/Versioning.props new file mode 100644 index 000000000..225f2ada2 --- /dev/null +++ b/tooling/Versioning.props @@ -0,0 +1,70 @@ + + + + + + 0 + + $(BonsaiVersion)-dev$(DevVersion) + <_FileVersionRevision>$([MSBuild]::Add(60000, $(DevVersion))) + BUILD_KIND_DEV + + + $(BonsaiVersion)$(CiBuildVersionSuffix) + $(CiBuildVersion) + <_FileVersionRevision>0 + <_FileVersionRevision Condition="'$(CiIsForRelease)' != 'true' and '$(CiRunNumber)' != ''">$(CiRunNumber) + + BUILD_KIND_UNSTABLE + BUILD_KIND_OFFICIAL_RELEASE + BUILD_KIND_PREVIEW + + + + BUILD_KIND_UNKNOWN + $(DefineConstants);$(BuildKindConstant) + + + $(WarningsAsErrors);CS7035 + + + + + $([System.Text.RegularExpressions.Regex]::Replace('$(Version)', '[\-\+].+$', '')).$(_FileVersionRevision) + + + + + 99.99.99 + 99.99.99.0 + 0000000000000000000000000000000000000000 + false + + + + <_UnchangedBonsaiVersion>$(Version) + <_UnchangedBonsaiFileVersion>$(FileVersion) + + + + + + + + + + \ No newline at end of file