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
66 changes: 60 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:
pip install .

- name: Build Python package
if: matrix.os == 'ubuntu-latest'
run: python -m build

- name: Publish to PyPI
Expand All @@ -53,16 +54,69 @@ jobs:
sudo apt-get install -y ruby ruby-dev build-essential rpm fakeroot tar libarchive-tools
sudo gem install --no-document fpm
make deb pacman rpm

- name: Install Rosetta (if needed)
if: matrix.os == 'macos-latest'
run: |
sudo /usr/sbin/softwareupdate --install-rosetta --agree-to-license || true
- name: Build universal binary with lipo (arm64 + x86_64)
if: matrix.os == 'macos-latest'
run: |
mkdir -p dist-universal
cp dist/xcsp dist-universal/xcsp-arm64
arch -x86_64 pyinstaller --onefile --name xcsp-x86_64 --paths=. bin/main.py
cp dist/xcsp-x86_64 dist-universal/xcsp-x86_64
lipo -create dist-universal/xcsp-arm64 dist-universal/xcsp-x86_64 -output dist-universal/xcsp
- name: Import Apple certificate
if: matrix.os == 'macos-latest'
run: |
echo "$CERT_P12_BASE64" | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain
env:
CERT_P12_BASE64: ${{ secrets.CERT_P12_BASE64 }}
CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }}
- name: Sign macOS binary
if: matrix.os == 'macos-latest'
run: |
codesign --timestamp --options runtime \
--sign "Developer ID Application" \
dist-universal/xcsp
- name: Notarize macOS binary with Apple
if: matrix.os == 'macos-latest'
run: |
xcrun notarytool submit dist-universal/xcsp \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_SPECIFIC_PWD" \
--team-id "$APPLE_TEAM_ID" \
--wait
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PWD: ${{ secrets.APPLE_APP_SPECIFIC_PWD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Staple notarization ticket
if: matrix.os == 'macos-latest'
run: |
xcrun stapler staple dist-universal/xcsp
- name: Package signed universal binary for Homebrew
if: matrix.os == 'macos-latest'
run: |
mkdir -p brew_tmp/bin brew_tmp/share/xcsp-launcher/configs brew_tmp/share/xcsp-launcher/tools
cp dist-universal/xcsp brew_tmp/bin/xcsp-macos
cp -r configs/* brew_tmp/share/xcsp-launcher/configs/
cp xcsp/tools/xcsp3-solutionChecker-2.5.jar brew_tmp/share/xcsp-launcher/tools/
tar -czvf xcsp-$(git describe --tags --abbrev=0)-macos.tar.gz -C brew_tmp .
- name: Homebrew (macOS only)
if: matrix.os == 'macos-latest'
run: |
make brew
sha256=$(shasum -a 256 xcsp-$(git describe --tags --abbrev=0)-macos.tar.gz | awk '{print $1}')
url="https://github.com/CPToolset/xcsp-launcher/releases/download/$(git describe --tags --abbrev=0)/xcsp-$(git describe --tags --abbrev=0)-macos.tar.gz"
sed -e "s|__URL__|$url|" -e "s|__SHASUM__|$sha256|" .packaging/homebrew/xcsp.rb.template > .packaging/homebrew/xcsp.rb
git clone --quiet https://x-access-token:${{ secrets.XCSP_GITHUB_TOKEN }}@github.com/CPToolset/homebrew-xcsp-launcher.git brew-tap
mkdir -p brew-tap/Formula/
cp .packaging/homebrew/xcsp.rb brew-tap/Formula/xcsp.rb
cd brew-tap && git add Formula/ && git commit -m "Update formula for version $(VERSION)" && git push
rm -rf brew-tap
cd brew-tap && git add Formula/xcsp.rb && git commit -m "Update formula for version $(git describe --tags --abbrev=0)" && git push
env:
GITHUB_TOKEN: ${{ secrets.XCSP_GITHUB_TOKEN }}

Expand All @@ -86,7 +140,7 @@ jobs:
*.deb
*.rpm
*.pkg.tar.*
*.tar.gz
xcsp-*-macos.tar.gz
*.snap
chocolatey/*.nupkg

Expand Down
66 changes: 33 additions & 33 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,39 +49,39 @@ deb: $(DIST_DIR)/$(BIN_NAME)
cp package/*.deb .
rm -rf package

# Créer une Formula Homebrew à partir du tar.gz
brew: $(DIST_DIR)/$(BIN_NAME)
@echo "Building Homebrew formula..."

# Générer un tar.gz contenant juste l'exécutable et les configs
mkdir -p brew_tmp/bin brew_tmp/share/xcsp-launcher/configs brew_tmp/share/xcsp-launcher/tools
cp $(DIST_DIR)/$(BIN_NAME) brew_tmp/bin/xcsp-macos
cp $(DIST_DIR)/${BIN_NAME} $(DIST_DIR)/xcsp-macos
cp -r configs/* brew_tmp/share/xcsp-launcher/configs/
cp xcsp/tools/xcsp3-solutionChecker-2.5.jar brew_tmp/share/xcsp-launcher/tools/xcsp3-solutionChecker-2.5.jar

# Créer archive
tar -czvf xcsp-$(VERSION:v%=%)-macos.tar.gz -C brew_tmp .

{ \
sha256=$$(shasum -a 256 xcsp-$(VERSION:v%=%)-macos.tar.gz | awk '{print $$1}'); \
url="https://github.com/CPToolset/xcsp-launcher/releases/download/$(VERSION)/xcsp-$(VERSION:v%=%)-macos.tar.gz"; \
sed \
-e "s|__URL__|$$url|" \
-e "s|__SHASUM__|$$sha256|" \
.packaging/homebrew/xcsp.rb.template > .packaging/homebrew/xcsp.rb; \
}

# Nettoyer temporaire
rm -rf brew_tmp

publish-brew: xcsp-*-macos.tar.gz
@echo "Publishing Homebrew Formula..."
git clone https://github.com/CPToolset/homebrew-xcsp-launcher.git brew-tap
mkdir -p brew-tap/Formula/
cp .packaging/homebrew/xcsp.rb brew-tap/Formula/xcsp.rb
cd brew-tap && git add Formula/ && git commit -m "Update formula for version $(VERSION)" && git push
rm -rf brew-tap
# # Créer une Formula Homebrew à partir du tar.gz
# brew: $(DIST_DIR)/$(BIN_NAME)
# @echo "Building Homebrew formula..."
#
# # Générer un tar.gz contenant juste l'exécutable et les configs
# mkdir -p brew_tmp/bin brew_tmp/share/xcsp-launcher/configs brew_tmp/share/xcsp-launcher/tools
# cp $(DIST_DIR)/$(BIN_NAME) brew_tmp/bin/xcsp-macos
# cp $(DIST_DIR)/${BIN_NAME} $(DIST_DIR)/xcsp-macos
# cp -r configs/* brew_tmp/share/xcsp-launcher/configs/
# cp xcsp/tools/xcsp3-solutionChecker-2.5.jar brew_tmp/share/xcsp-launcher/tools/xcsp3-solutionChecker-2.5.jar
#
# # Créer archive
# tar -czvf xcsp-$(VERSION:v%=%)-macos.tar.gz -C brew_tmp .
#
# { \
# sha256=$$(shasum -a 256 xcsp-$(VERSION:v%=%)-macos.tar.gz | awk '{print $$1}'); \
# url="https://github.com/CPToolset/xcsp-launcher/releases/download/$(VERSION)/xcsp-$(VERSION:v%=%)-macos.tar.gz"; \
# sed \
# -e "s|__URL__|$$url|" \
# -e "s|__SHASUM__|$$sha256|" \
# .packaging/homebrew/xcsp.rb.template > .packaging/homebrew/xcsp.rb; \
# }
#
# # Nettoyer temporaire
# rm -rf brew_tmp
#
# publish-brew: xcsp-*-macos.tar.gz
# @echo "Publishing Homebrew Formula..."
# git clone https://github.com/CPToolset/homebrew-xcsp-launcher.git brew-tap
# mkdir -p brew-tap/Formula/
# cp .packaging/homebrew/xcsp.rb brew-tap/Formula/xcsp.rb
# cd brew-tap && git add Formula/ && git commit -m "Update formula for version $(VERSION)" && git push
# rm -rf brew-tap

pacman: $(DIST_DIR)/$(BIN_NAME)
mkdir -p package/usr/bin
Expand Down
2 changes: 1 addition & 1 deletion configs
8 changes: 4 additions & 4 deletions tests/test_installed_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def test_with_xcsp_file_sat(self, solver, instance):
print(f"{solver} is not present in solutions.json file. We skip test.",file=sys.stderr)
return
solution_for_current_instance = solutions[solver][instance.split("/")[-1]]
print(f"Test of ace with input {instance}", file=sys.stderr)
print(f"Test of {solver} with input {instance}", file=sys.stderr)
for index, o in enumerate(solution_for_current_instance["solutions"]):
solver = Solver.lookup("ace")
solver = Solver.lookup(solver)
solver.set_limit_number_of_solutions(index + 1)
solver.solve(instance)
assert solver.objective_value() is not None
Expand All @@ -59,7 +59,7 @@ def test_with_xcsp_file_sat(self, solver, instance):
(solver, instance) for solver in solvers for instance in instances_unsat
])
def test_with_xcsp_file_unsat(self, solver, instance):
print(f"Test of ace with input {instance}", file=sys.stderr)
print(f"Test of {solver} with input {instance}", file=sys.stderr)
solver = Solver.lookup(solver)
solver.solve(instance)
assert solver.objective_value() is None
Expand All @@ -69,7 +69,7 @@ def test_with_xcsp_file_unsat(self, solver, instance):
(solver, instance) for solver in solvers for instance in instances_unknown
])
def test_with_xcsp_file_unknown(self, solver, instance):
print(f"Test of ace with input {instance}", file=sys.stderr)
print(f"Test of {solver} with input {instance}", file=sys.stderr)
solver = Solver.lookup(solver)
solver.set_time_limit(10)
solver.solve(instance)
Expand Down
41 changes: 34 additions & 7 deletions xcsp/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
from loguru import logger
from timeit import default_timer as timer

from packaging.version import Version

from xcsp.builder.build import AutoBuildStrategy, ManualBuildStrategy
from xcsp.builder.check import check_available_builder_for_language, MAP_FILE_LANGUAGE, MAP_LANGUAGE_FILES, MAP_BUILDER
from xcsp.utils.archive import ALL_ARCHIVE_EXTENSIONS, extract_archive
Expand Down Expand Up @@ -135,6 +137,17 @@ def build_cmd(config, bin_executable, bin_dir):
return result_cmd


def keep_only_semver_versions(all_versions):
results = []
for v in all_versions:
try:
_ = Version(v)
results.append(v)
except Exception as e:
continue
return sort_versions(results)


class Installer:
"""Main class responsible for installing a solver from a repository."""

Expand All @@ -144,7 +157,7 @@ def __init__(self, url: str, solver_name: str, id_s: str, config=None):
self._id = id_s
self._path_solver = None
self._start_time = timer()
self._repo = None
self._repo: VersionDirectory = None
self._config = config
self._config_strategy = None
self._mode_build_strategy = None
Expand Down Expand Up @@ -222,7 +235,7 @@ def _manage_dependency(self):
def _manage_git_dependency(self, dep, git_url):
name = git_url.split("/")[-1].replace(".git", "")
default_dir = self._path_solver.parent.parent / "deps" / name
target_dir = replace_solver_dir_in_str(dep.get("dir"), str(self._repo.get_source_path)) if dep.get(
target_dir = replace_solver_dir_in_str(dep.get("dir"), str(self._repo.get_source_path())) if dep.get(
"dir") else default_dir
target_dir = Path(target_dir)
target_dir.parent.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -327,13 +340,26 @@ def install(self):
f"Please manually copy your binaries into {bin_dir}.")
continue
executable_path = Path(v['executable'])
result_path = shutil.copy(Path(self._repo.get_source_path()) / v["executable"],
bin_dir / executable_path.name)
logger.success(f"Executable for version '{v['version']}' successfully copied to {result_path}.")
final_placeholder_for_executable= executable_path.name
if executable_path.is_dir():
logger.info(f"Copying content of directory '{executable_path}' to binary directory '{bin_dir}'.")
for item in (Path(self._repo.get_source_path()) / executable_path).iterdir():
dest = bin_dir / item.name
shutil.copy(item.absolute(), dest)
final_placeholder_for_executable = bin_dir
logger.success(f"Directory for version '{v['version']}' successfully copied to {bin_dir}.")
elif executable_path.is_file():
result_path = shutil.copy(Path(self._repo.get_source_path()) / v["executable"],
bin_dir / executable_path.name)
final_placeholder_for_executable = bin_dir/executable_path.name
logger.success(f"Executable for version '{v['version']}' successfully copied to {result_path}.")

if self._config is not None and self._config.get("command") is not None:
result_cmd = build_cmd(self._config, final_placeholder_for_executable , bin_dir)
logger.debug(result_cmd)
CACHE[self._id]["versions"][v['version']] = {
"options": self._config["command"].get("options", dict()),
"cmd": build_cmd(self._config, bin_dir / executable_path.name, bin_dir),
"cmd": build_cmd(self._config, final_placeholder_for_executable , bin_dir),
"alias": v.get("alias", list())
}
have_latest = have_latest or "latest" in v.get("alias", []) or v.get("version") == "latest" or v.get("git_tag") == "latest"
Expand Down Expand Up @@ -362,7 +388,8 @@ def install(self):
logger.info(f"Restoring original repository (if needed)...")
self._repo.restore()
logger.info(f"Version '{v['version']}' end ... {timer() - version_timer:.2f} seconds.")
list_versions = sort_versions(list(CACHE[self._id]["versions"].keys()))
all_versions = list(CACHE[self._id]["versions"].keys())
list_versions = keep_only_semver_versions(all_versions)
if not have_latest and len(list_versions)>0:
latest = list_versions[-1]
logger.debug(list_versions)
Expand Down