Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup and modernize the wrapper #52

Merged
merged 13 commits into from
Dec 4, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Build OSQP interface
uses: matlab-actions/run-command@v1
with:
command: make_osqp
command: osqp.build('osqp_mex')

- name: Run tests
uses: matlab-actions/run-tests@v1
Expand Down
164 changes: 164 additions & 0 deletions @osqp/build.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
function build(varargin)
% Matlab MEX makefile for OSQP.
%
% MAKE_OSQP(VARARGIN) is a make file for OSQP solver. It
% builds OSQP and its components from source.
%
% WHAT is the last element of VARARGIN and cell array of strings,
% with the following options:
%
% {}, '' (empty string) or 'all': build all components and link.
%
% 'osqp_mex': builds the OSQP mex interface and the OSQP library
%
% Additional commands:
%
% 'clean': Delete all compiled files
% 'purge': Delete all compiled files and copied code generation files

if( nargin == 0 )
what = {'all'};
verbose = false;
elseif ( nargin == 1 && ismember('-verbose', varargin) )
what = {'all'};
verbose = true;
else
what = varargin{nargin};
if(isempty(strfind(what, 'all')) && ...
isempty(strfind(what, 'osqp_mex')) && ...
isempty(strfind(what, 'clean')) && ...
isempty(strfind(what, 'purge')))
fprintf('No rule to make target "%s", exiting.\n', what);
end

verbose = ismember('-verbose', varargin);
end

%% Determine where the various files are all located
% Various parts of the build system
[osqp_classpath,~,~] = fileparts( mfilename( 'fullpath' ) );
osqp_mex_src_dir = fullfile( osqp_classpath, '..', 'c_sources' );
osqp_mex_build_dir = fullfile( osqp_mex_src_dir, 'build' );
osqp_cg_src_dir = fullfile( osqp_mex_build_dir, 'codegen_src' );
osqp_cg_dest_dir = fullfile( osqp_classpath, '..', 'codegen', 'sources' );

% Determine where CMake should look for MATLAB
Matlab_ROOT = strrep( matlabroot, '\', '/' );

%% Try to unlock any pre-existing version of osqp_mex
% this prevents compile errors if a user builds, runs osqp
% and then tries to recompile
if(mislocked('osqp_mex'))
munlock('osqp_mex');
end

%% Configure, build and install the OSQP mex interface
if( any(strcmpi(what,'osqp_mex')) || any(strcmpi(what,'all')) )
fprintf('Compiling OSQP solver mex interface...\n');

% Create build for the mex file and go inside
if exist( osqp_mex_build_dir, 'dir' )
rmdir( osqp_mex_build_dir, 's' );
end
mkdir( osqp_mex_build_dir );
% cd( osqp_mex_build_dir );

% Extend path for CMake mac (via Homebrew)
PATH = getenv('PATH');
if( (ismac) && (isempty(strfind(PATH, '/usr/local/bin'))) )
setenv('PATH', [PATH ':/usr/local/bin']);
end



%% Configure CMake for the mex interface
fprintf(' Configuring...' )
[status, output] = system( sprintf( 'cmake -B %s -S %s -DCMAKE_BUILD_TYPE=RelWithDebInfo -DMatlab_ROOT_DIR=\"%s\"', osqp_mex_build_dir, osqp_mex_src_dir, Matlab_ROOT ), 'LD_LIBRARY_PATH', '' );
if( status )
fprintf( '\n' );
disp( output );
error( 'Error configuring CMake environment' );
elseif( verbose )
fprintf( '\n' );
disp( output );
else
fprintf( '\t\t\t\t\t[done]\n' );
end

%% Build the mex interface
fprintf( ' Building...')
[status, output] = system( sprintf( 'cmake --build %s --config Release', osqp_mex_build_dir ), 'LD_LIBRARY_PATH', '' );
if( status )
fprintf( '\n' );
disp( output );
error( 'Error compiling OSQP mex interface' );
elseif( verbose )
fprintf( '\n' );
disp( output );
else
fprintf( '\t\t\t\t\t\t[done]\n' );
end


%% Install various files
fprintf( ' Installing...' )

% Copy mex file to root directory for use
if( ispc )
[err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'Release', filesep, 'osqp_mex.mex*'], [osqp_classpath, filesep, 'private'] );
else
[err, errmsg, ~] = copyfile( [osqp_mex_build_dir, filesep, 'osqp_mex.mex*'], [osqp_classpath, filesep, 'private'] );
end
if( ~err )
fprintf( '\n' )
disp( errmsg )
error( ' Error copying mex file' )
end

% Copy the code generation source files
% Create build for the mex file and go inside
if exist( osqp_cg_dest_dir, 'dir' )
rmdir( osqp_cg_dest_dir, 's' );
end
mkdir( osqp_cg_dest_dir );

[err, errmsg, ~] = copyfile( [osqp_cg_src_dir, filesep, '*'], osqp_cg_dest_dir );
if( ~err )
fprintf( '\n' )
disp( errmsg )
error( ' Error copying code generation source files' )
end

fprintf( '\t\t\t\t\t\t[done]\n' );
end

%% Clean and purge
if( any(strcmpi(what,'clean')) || any(strcmpi(what,'purge')) )
fprintf('Cleaning OSQP mex files and build directory...');

% Delete mex file
mexfiles = dir(['*.', mexext]);
for i = 1 : length(mexfiles)
delete(mexfiles(i).name);
end

% Delete OSQP build directory
if exist(osqp_mex_build_dir, 'dir')
rmdir(osqp_mex_build_dir, 's');
end

fprintf('\t\t[done]\n');

%% Purge only
if( any(strcmpi(what,'purge')) )
fprintf('Cleaning OSQP codegen directories...');

% Delete codegen files
if exist(osqp_cg_dest_dir, 'dir')
rmdir(osqp_cg_dest_dir, 's');
end

fprintf('\t\t\t[done]\n');
end
end
end
208 changes: 208 additions & 0 deletions @osqp/codegen.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
%%
function codegen(this, target_dir, varargin)
% CODEGEN generate C code for the parametric problem
%
% codegen(target_dir,options)

% Parse input arguments
p = inputParser;
defaultProject = '';
expectedProject = {'', 'Makefile', 'MinGW Makefiles', 'Unix Makefiles', 'CodeBlocks', 'Xcode'};
defaultParams = 'vectors';
expectedParams = {'vectors', 'matrices'};
defaultMexname = 'emosqp';
defaultFloat = false;
defaultLong = true;
defaultFW = false;

addRequired(p, 'target_dir', @isstr);
addParameter(p, 'project_type', defaultProject, ...
@(x) ischar(validatestring(x, expectedProject)));
addParameter(p, 'parameters', defaultParams, ...
@(x) ischar(validatestring(x, expectedParams)));
addParameter(p, 'mexname', defaultMexname, @isstr);
addParameter(p, 'FLOAT', defaultFloat, @islogical);
addParameter(p, 'LONG', defaultLong, @islogical);
addParameter(p, 'force_rewrite', defaultFW, @islogical);

parse(p, target_dir, varargin{:});

% Set internal variables
if strcmp(p.Results.parameters, 'vectors')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use Matlab enumeration instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire file needs to be rewritten to support the newer codegen paradigm anyway, so I didn't actually change anything in there. We should do this when we update that file though.

embedded = 1;
else
embedded = 2;
end
if p.Results.FLOAT
float_flag = 'ON';
else
float_flag = 'OFF';
end
if p.Results.LONG
long_flag = 'ON';
else
long_flag = 'OFF';
end
if strcmp(p.Results.project_type, 'Makefile')
if (ispc)
project_type = 'MinGW Makefiles'; % Windows
elseif (ismac || isunix)
project_type = 'Unix Makefiles'; % Unix
end
else
project_type = p.Results.project_type;
end

% Check whether the specified directory already exists
if exist(target_dir, 'dir')
if p.Results.force_rewrite
rmdir(target_dir, 's');
else
while(1)
prompt = sprintf('Directory "%s" already exists. Do you want to replace it? y/n [y]: ', target_dir);
str = input(prompt, 's');

if any(strcmpi(str, {'','y'}))
rmdir(target_dir, 's');
break;
elseif strcmpi(str, 'n')
return;
end
end
end
end

% Import OSQP path
[osqp_path,~,~] = fileparts(which('osqp.m'));

% Add codegen directory to path
addpath(fullfile(osqp_path, 'codegen'));

% Path of osqp module
cg_dir = fullfile(osqp_path, 'codegen');
files_to_generate_path = fullfile(cg_dir, 'files_to_generate');

% Get workspace structure
work = osqp_mex('get_workspace', this.objectHandle);

% Make target directory
fprintf('Creating target directories...\t\t\t\t\t');
target_configure_dir = fullfile(target_dir, 'configure');
target_include_dir = fullfile(target_dir, 'include');
target_src_dir = fullfile(target_dir, 'src');

if ~exist(target_dir, 'dir')
mkdir(target_dir);
end
if ~exist(target_configure_dir, 'dir')
mkdir(target_configure_dir);
end
if ~exist(target_include_dir, 'dir')
mkdir(target_include_dir);
end
if ~exist(target_src_dir, 'dir')
mkdir(fullfile(target_src_dir, 'osqp'));
end
fprintf('[done]\n');

% Copy source files to target directory
fprintf('Copying OSQP source files...\t\t\t\t\t');
cdir = fullfile(cg_dir, 'sources', 'src');
cfiles = dir(fullfile(cdir, '*.c'));
for i = 1 : length(cfiles)
if embedded == 1
% Do not copy kkt.c if embedded is 1
if ~strcmp(cfiles(i).name, 'kkt.c')
copyfile(fullfile(cdir, cfiles(i).name), ...
fullfile(target_src_dir, 'osqp', cfiles(i).name));
end
else
copyfile(fullfile(cdir, cfiles(i).name), ...
fullfile(target_src_dir, 'osqp', cfiles(i).name));
end
end
configure_dir = fullfile(cg_dir, 'sources', 'configure');
configure_files = dir(fullfile(configure_dir, '*.h.in'));
for i = 1 : length(configure_files)
copyfile(fullfile(configure_dir, configure_files(i).name), ...
fullfile(target_configure_dir, configure_files(i).name));
end
hdir = fullfile(cg_dir, 'sources', 'include');
hfiles = dir(fullfile(hdir, '*.h'));
for i = 1 : length(hfiles)
if embedded == 1
% Do not copy kkt.h if embedded is 1
if ~strcmp(hfiles(i).name, 'kkt.h')
copyfile(fullfile(hdir, hfiles(i).name), ...
fullfile(target_include_dir, hfiles(i).name));
end
else
copyfile(fullfile(hdir, hfiles(i).name), ...
fullfile(target_include_dir, hfiles(i).name));
end
end

% Copy cmake files
copyfile(fullfile(cdir, 'CMakeLists.txt'), ...
fullfile(target_src_dir, 'osqp', 'CMakeLists.txt'));
copyfile(fullfile(hdir, 'CMakeLists.txt'), ...
fullfile(target_include_dir, 'CMakeLists.txt'));
fprintf('[done]\n');

% Copy example.c
copyfile(fullfile(files_to_generate_path, 'example.c'), target_src_dir);

% Render CMakeLists.txt
fidi = fopen(fullfile(files_to_generate_path, 'CMakeLists.txt'),'r');
fido = fopen(fullfile(target_dir, 'CMakeLists.txt'),'w');
while ~feof(fidi)
l = fgetl(fidi); % read line
% Replace EMBEDDED_FLAG in CMakeLists.txt by a numerical value
newl = strrep(l, 'EMBEDDED_FLAG', num2str(embedded));
fprintf(fido, '%s\n', newl);
end
fclose(fidi);
fclose(fido);

% Render workspace.h and workspace.c
work_hfile = fullfile(target_include_dir, 'workspace.h');
work_cfile = fullfile(target_src_dir, 'osqp', 'workspace.c');
fprintf('Generating workspace.h/.c...\t\t\t\t\t\t');
render_workspace(work, work_hfile, work_cfile, embedded);
fprintf('[done]\n');

% Create project
if ~isempty(project_type)

% Extend path for CMake mac (via Homebrew)
PATH = getenv('PATH');
if ((ismac) && (isempty(strfind(PATH, '/usr/local/bin'))))
setenv('PATH', [PATH ':/usr/local/bin']);
end

fprintf('Creating project...\t\t\t\t\t\t\t\t');
orig_dir = pwd;
cd(target_dir);
mkdir('build')
cd('build');
cmd = sprintf('cmake -G "%s" ..', project_type);
[status, output] = system(cmd);
if(status)
fprintf('\n');
fprintf(output);
error('Error configuring CMake environment');
else
fprintf('[done]\n');
end
cd(orig_dir);
end

% Make mex interface to the generated code
mex_cfile = fullfile(files_to_generate_path, 'emosqp_mex.c');
make_emosqp(target_dir, mex_cfile, embedded, float_flag, long_flag);

% Rename the mex file
old_mexfile = ['emosqp_mex.', mexext];
new_mexfile = [p.Results.mexname, '.', mexext];
movefile(old_mexfile, new_mexfile);
end
Loading