Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 117 additions & 15 deletions .github/workflows/full-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ jobs:
container:
image: ${{ matrix.container_image }}
options: ${{ matrix.container_options }} --volume /mnt:/mnt
permissions:
contents: write
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -363,7 +365,7 @@ jobs:

if [ $overall_exit_code -ne 0 ]; then
echo "Rerunning failing tests..."
ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output || overall_exit_code=$?
ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output && overall_exit_code=0 || overall_exit_code=$?
fi
echo "exit_code=${overall_exit_code}" >> $GITHUB_OUTPUT

Expand Down Expand Up @@ -464,7 +466,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: OS-Installers-${{ matrix.platform }}-TGZ-${{ github.sha }}
path: ${{ steps.build_path.outputs.path }}/*.tar.gz
path: ${{ steps.build_path.outputs.path }}/OpenStudio-*.tar.gz
if-no-files-found: ignore

- name: Upload WHEEL installer
Expand All @@ -478,7 +480,7 @@ jobs:
name: Publish Linux Artifacts
needs: [linux-build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
steps:
- name: Download all installers
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -510,6 +512,80 @@ jobs:
if command -v md5sum >/dev/null 2>&1; then md5sum "$file"; else md5 "$file"; fi
done

- name: Trigger Docker Build
if: inputs.skip_docker_trigger != 'true' && github.event.inputs.skip_docker_trigger != 'true'
working-directory: installers
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH_NAME: ${{ github.ref_name }}
S3_PREFIX: ${{ github.ref_type == 'tag' && format('releases/{0}', github.ref_name) || format('{0}', github.ref_name) }}
run: |
set -euo pipefail

# Find the 22.04 deb file locally
DEB_FILE=$(find . -name "*22.04*.deb" | head -n 1)
if [ -z "$DEB_FILE" ]; then
echo "::error::Could not find Ubuntu 22.04 deb file in installers directory"
ls -la
exit 1
fi

FILENAME=$(basename "$DEB_FILE")
echo "Found file: $FILENAME"

# Construct S3 URL
# Replace + with %2B
ENCODED_FILENAME=${FILENAME//+/%2B}
BINARY_URL="https://${AWS_S3_BUCKET}.s3.amazonaws.com/${S3_PREFIX}/${ENCODED_FILENAME}"
echo "Binary URL: $BINARY_URL"

# Parse version from filename
# Expected format: OpenStudio-<Version>-<Platform>.deb
if [[ "$FILENAME" =~ OpenStudio-(.+)-Ubuntu-22\.04.*\.deb ]]; then
OS_VERSION_FULL=${BASH_REMATCH[1]}
elif [[ "$FILENAME" =~ OpenStudio-(.+)-Linux.*\.deb ]]; then
OS_VERSION_FULL=${BASH_REMATCH[1]}
else
echo "::error::Could not parse version from filename: $FILENAME"
exit 1
fi

echo "Full Version: $OS_VERSION_FULL"

# Logic from Jenkins:
# Split by +
IFS='+' read -r VER_PART SHA_PART <<< "$OS_VERSION_FULL"

if [[ "$VER_PART" == *"-"* ]]; then
# 3.3.0-rc1 -> Ver: 3.3.0, Ext: rc1
IFS='-' read -r OS_VERSION OS_VERSION_EXT <<< "$VER_PART"
else
# 3.3.0+sha -> Ver: 3.3.0, Ext: sha
OS_VERSION="$VER_PART"
OS_VERSION_EXT="$SHA_PART"
fi

echo "OS Version: $OS_VERSION"
echo "OS Version Ext: $OS_VERSION_EXT"

# Docker Tag Logic
if [[ "$BRANCH_NAME" == "develop" ]]; then
DOCKER_IMAGE_TAG="develop"
else
DOCKER_IMAGE_TAG="${OS_VERSION}-${OS_VERSION_EXT}"
fi
echo "Docker Image Tag: $DOCKER_IMAGE_TAG"

# Trigger Workflow
echo "Triggering manual_update_develop workflow in NREL/docker-openstudio..."
gh workflow run 'manual_update_develop' \
--repo NREL/docker-openstudio \
--ref develop \
-f docker_image_tag="$DOCKER_IMAGE_TAG" \
-f os_installer_link="$BINARY_URL" \
-f os_version="$OS_VERSION" \
-f os_version_ext="$OS_VERSION_EXT"

macos-build:
name: Build Packages for ${{ matrix.pretty }}
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -645,7 +721,7 @@ jobs:

- name: Create Build Directory
run: cmake -E make_directory ${{ env.OPENSTUDIO_BUILD }}

- name: Configure Conan remotes
run: |
set -euo pipefail
Expand Down Expand Up @@ -797,13 +873,13 @@ jobs:

if [ $overall_exit_code -ne 0 ]; then
echo "Rerunning failing tests..."
ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output --output-on-failure || overall_exit_code=$?
ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test --no-compress-output --output-on-failure && overall_exit_code=0 || overall_exit_code=$?
fi

echo "exit_code=${overall_exit_code}" >> $GITHUB_OUTPUT

- name: Setup Keychain
if: ${{ success() && !cancelled() && (github.ref == 'refs/heads/develop' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' || matrix.os == 'macOS') }}
if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' )
env:
APPLE_CERT_DATA: ${{ secrets.APPLE_CERT_DATA }}
APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
Expand All @@ -829,6 +905,8 @@ jobs:
- name: Create packages
if: ${{ success() && !cancelled() }}
working-directory: ${{ env.OPENSTUDIO_BUILD }}
env:
COPYFILE_DISABLE: 1
run: |
set -euo pipefail
. ./conanbuild.sh
Expand All @@ -841,6 +919,28 @@ jobs:
find . -name "*.o" -type f -delete || true
df -h .

- name: Ad-hoc Sign Inner Installer (Fix "Killed" Error)
if: ${{ success() && !cancelled() }}
working-directory: ${{ env.OPENSTUDIO_BUILD }}
env:
# Check if we have a real ID; if not, we must patch the installer
APPLE_DEV_ID: ${{ secrets.APPLE_DEV_ID }}
run: |
set -euo pipefail

# Only run this fix if we DO NOT have a valid Developer ID.
if [ -n "$APPLE_DEV_ID" ]; then
echo "Valid Developer ID detected. Skipping ad-hoc patch."
exit 0
fi

echo "No Developer ID found. Patching DMGs with ad-hoc signature..."

# Loop through all generated DMGs
find . -maxdepth 1 -name "*.dmg" -print0 | while IFS= read -r -d '' dmg_file; do
../${{ env.OPENSTUDIO_SOURCE }}/developer/scripts/patch_adhoc_dmg.sh "$dmg_file"
done

- name: Sign DMG and Notarize
if: ${{ steps.create_packages.outcome == 'success' && (github.ref == 'refs/heads/develop' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true' || matrix.os == 'macOS') }}
working-directory: ${{ env.OPENSTUDIO_BUILD }}
Expand Down Expand Up @@ -980,7 +1080,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: OS-TGZ-${{ matrix.platform }}-${{ github.sha }}
path: ${{ env.OPENSTUDIO_BUILD }}/*.tar.gz
path: ${{ env.OPENSTUDIO_BUILD }}/OpenStudio-*.tar.gz
if-no-files-found: ignore

- name: Fail job on test failures
Expand All @@ -992,7 +1092,7 @@ jobs:
name: Publish MacOS Artifacts
needs: [macos-build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
steps:
- name: Download all installers
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -1028,6 +1128,8 @@ jobs:
windows-build:
name: Build ${{ matrix.pretty }}
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -1307,7 +1409,7 @@ jobs:
if ($overall_exit_code -ne 0) {
Write-Host "Rerunning failing tests..."
ctest -C ${{ env.BUILD_TYPE }} --rerun-failed -T test
if ($LASTEXITCODE -ne 0) { $overall_exit_code = 1 }
if ($LASTEXITCODE -eq 0) { $overall_exit_code = 0 } else { $overall_exit_code = 1 }
}
"exit_code=$overall_exit_code" | Out-File -FilePath $env:GITHUB_OUTPUT -Append

Expand Down Expand Up @@ -1392,25 +1494,25 @@ jobs:

# CODE SIGNING - AWS Signing Service
- name: Setup Node.js
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
uses: actions/setup-node@v4
with:
node-version: "18"

- name: Install Signing Client Dependencies
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
run: npm install
working-directory: ./.github/signing-client

- name: Create .env file for Signing
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
run: |
echo "ACCESS_KEY=${{ secrets.AWS_SIGNING_ACCESS_KEY }}" > .env
echo "SECRET_KEY=${{ secrets.AWS_SIGNING_SECRET_KEY }}" >> .env
working-directory: ${{ env.OPENSTUDIO_BUILD }}

- name: Code sign installer
if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true')
if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true')
working-directory: ${{ env.OPENSTUDIO_BUILD }}
run: |
# Check if signing client exists
Expand Down Expand Up @@ -1517,7 +1619,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: OS-Installers-${{ matrix.platform }}-TGZ-${{ github.sha }}
path: ${{ env.OPENSTUDIO_BUILD }}/*.tar.gz
path: ${{ env.OPENSTUDIO_BUILD }}/OpenStudio-*.tar.gz
if-no-files-found: ignore

- name: Upload Signed installers
Expand All @@ -1531,7 +1633,7 @@ jobs:
name: Publish Windows Artifacts
needs: [windows-build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.publish_to_s3 == 'true' || github.event.inputs.publish_to_s3 == 'true'
steps:
- name: Download all installers
uses: actions/download-artifact@v4
Expand Down
28 changes: 9 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,12 @@ More information and documentation is available at the [OpenStudio website](http

For development builds (artifacts downloaded from GitHub Actions), you may encounter a "Damaged" error or "Unidentified Developer" warning on macOS, especially on Apple Silicon (ARM) machines. This is because these builds are not notarized by Apple.

If you encounter these issues, standard `xattr` commands on the DMG may not be sufficient. Please follow these steps:

1. **Mount the DMG** image.
2. **Copy** the Installer application (e.g., `OpenStudio-3.11.0...app`) from the mounted volume to a local folder (e.g., your `Downloads` folder). *Do not run it directly from the DMG.*
3. Open a **Terminal** and run the following commands on the *local copy* of the installer:

```bash
# 1. Remove quarantine attributes
xattr -cr path/to/local/OpenStudio-Installer.app

# 2. Ad-hoc sign the application (fixes "Killed" or crashes on startup)
codesign --force --deep --sign - path/to/local/OpenStudio-Installer.app
```

4. Run the installer. If double-clicking fails, run the executable directly with `sudo`:

```bash
sudo path/to/local/OpenStudio-Installer.app/Contents/MacOS/OpenStudio-3.11.0-<version>-Darwin-<arch>
```
If you encounter these issues, please follow these steps to bypass the security check for this specific installer:

1. **Mount the DMG**: Locate the downloaded `.dmg` file in Finder. Right-click (or Control-click) the file and select **Open**.
2. **Launch Installer**: Inside the mounted disk image window, Right-click (or Control-click) the `OpenStudio-Installer.app` file and select **Open**.
3. **Acknowledge Warning**: A security warning dialog will appear. Click **Open** if available. If only **OK** is available, click it (the installer might close).
4. **Security Settings**: Open **System Settings** (or System Preferences) and navigate to **Privacy & Security**.
5. **Allow the App**: Scroll down to the "Security" section. Look for a note about the OpenStudio application being blocked. Click the **Open Anyway** button.
6. **Confirm Open**: A final confirmation dialog will appear. Click **Open**.
7. **Authenticate**: Enter your system password when prompted to authorize the installation.
100 changes: 100 additions & 0 deletions developer/scripts/patch_adhoc_dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/bin/bash
set -euo pipefail

if [ "$#" -ne 1 ]; then
echo "Usage: $0 <dmg_file>"
exit 1
fi

DMG_FILE="$1"
MOUNT_POINT="mount_point_$(date +%s)"
TEMP_RW="temp_rw_$(date +%s).dmg"

echo "-------------------------------------------------------"
echo "Target DMG: $DMG_FILE"
echo "-------------------------------------------------------"

# 1. Convert to Read-Write
echo "Converting to Read-Write..."
rm -f "$TEMP_RW"
hdiutil convert "$DMG_FILE" -format UDRW -o "$TEMP_RW" -quiet

# 2. Resize DMG
# Ad-hoc signatures add metadata. We resize to 2GB to be absolutely sure there's space.
# For DMGs with partition maps (like CPack's GUID images), standard resize works.
# For flat images, -imageonly might be needed.
echo "Resizing DMG..."
if hdiutil resize -size 2g "$TEMP_RW" 2>/dev/null; then
echo " Standard resize to 2GB succeeded."
elif hdiutil resize -size 2g -imageonly "$TEMP_RW" 2>/dev/null; then
echo " Image-only resize to 2GB succeeded."
else
echo "Warning: Resize failed, proceeding anyway. This may cause 'internal error' in codesign if space is tight."
fi

# 3. Mount
echo "Mounting..."
mkdir -p "$MOUNT_POINT"
hdiutil attach "$TEMP_RW" -mountpoint "$MOUNT_POINT" -nobrowse -noverify -quiet

# 4. Sign
echo "Applying ad-hoc signature..."
export CODESIGN_ALLOCATE=$(xcrun -find codesign_allocate)

# Find all .app bundles in the mount point
find "$MOUNT_POINT" -maxdepth 1 -name "*.app" -print0 | while IFS= read -r -d '' app_path; do
echo "Processing app bundle: $app_path"

# Ensure everything is writable
echo "Ensuring app bundle is writable..."
chmod -R u+w "$app_path" || true

# 1. Clear extended attributes
echo "Clearing extended attributes..."
xattr -cr "$app_path" || echo "Warning: Some extended attributes could not be cleared."

# 2. Sign nested components
echo "Signing nested components..."
# Sign in depth-first order so that inner items are signed before their containers
find "$app_path" -depth -print0 \( \
\( -type d -name "*.framework" \) -o \
\( -type d -name "*.bundle" \) -o \
\( -type d -name "*.plugin" \) -o \
\( -type f -name "*.dylib" \) -o \
\( -type f -perm -111 \) \
\) | while IFS= read -r -d '' item; do
if [ "$item" != "$app_path" ]; then
# Double check it's actually signable (directory bundle or Mach-O file)
if [ -d "$item" ]; then
echo " Signing nested bundle: $item"
codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item" || true
elif [ -f "$item" ]; then
if file "$item" | grep -qE "Mach-O|current ar archive"; then
echo " Signing nested binary: $item"
# Try to remove signature first if it fails with internal error
codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item" || {
echo " Failed to sign $item, attempting signature removal first..."
codesign --remove-signature "$item" || true
codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$item"
}
fi
fi
fi
done

echo "Signing top-level app..."
codesign --force --verify --verbose --sign - --timestamp=none --generate-entitlement-der "$app_path"
done

# 5. Cleanup and Convert Back
echo "Detaching..."
hdiutil detach "$MOUNT_POINT" -force
rmdir "$MOUNT_POINT"

echo "Converting back to compressed DMG..."
FINAL_DMG="signed_repacked.dmg"
hdiutil convert "$TEMP_RW" -format UDZO -o "$FINAL_DMG" -quiet
mv "$FINAL_DMG" "$DMG_FILE"
rm "$TEMP_RW"

echo "Success: $DMG_FILE updated."
Loading