Skip to content

Commit

Permalink
Merge pull request #52 from osqp/im/restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
imciner2 authored Dec 4, 2023
2 parents fd75800 + 38b984f commit 391fe8f
Show file tree
Hide file tree
Showing 25 changed files with 1,501 additions and 1,385 deletions.
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')
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

0 comments on commit 391fe8f

Please sign in to comment.